├── .rspec ├── test ├── integration │ ├── default │ │ ├── controls │ │ │ ├── ssh-baseline.rb │ │ │ └── deprecations.rb │ │ └── inspec.yml │ └── without-pam │ │ └── ssh_password_selinux_spec.rb └── fixtures │ ├── vagrantfiles │ └── enforce_selinux.rb │ └── cookbooks │ └── test │ ├── metadata.rb │ └── recipes │ └── default.rb ├── templates └── default │ ├── authorized_keys.erb │ ├── openssh.conf.erb │ └── opensshd.conf.erb ├── Berksfile ├── files └── default │ └── ssh_password.te ├── .github ├── workflows │ └── codespell.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── renovate.json ├── Gemfile ├── .rubocop.yml ├── recipes ├── default.rb ├── unlock.rb ├── client.rb └── server.rb ├── .travis.yml ├── spec ├── recipes │ ├── unlock_spec.rb │ ├── default_spec.rb │ ├── client_spec.rb │ └── server_spec.rb ├── spec_helper.rb ├── shared_examples_crypto.rb └── libraries │ └── devsec_ssh_spec.rb ├── .kitchen.yml ├── chefignore ├── metadata.rb ├── Rakefile ├── .kitchen.dokken.yml ├── CONTRIBUTING.md ├── attributes └── default.rb ├── libraries └── devsec_ssh.rb ├── LICENSE ├── README.md └── CHANGELOG.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /test/integration/default/controls/ssh-baseline.rb: -------------------------------------------------------------------------------- 1 | include_controls 'ssh-baseline' 2 | -------------------------------------------------------------------------------- /templates/default/authorized_keys.erb: -------------------------------------------------------------------------------- 1 | <% @keys.each do |key| %> 2 | <%= key %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /test/integration/default/inspec.yml: -------------------------------------------------------------------------------- 1 | name: ssh-hardening-integration-tests 2 | version: 1.0.0 3 | depends: 4 | - name: ssh-baseline 5 | url: https://github.com/dev-sec/ssh-baseline 6 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | source 'https://supermarket.chef.io' 4 | 5 | metadata 6 | 7 | group :test do 8 | cookbook 'test', path: 'test/fixtures/cookbooks/test' 9 | end 10 | -------------------------------------------------------------------------------- /files/default/ssh_password.te: -------------------------------------------------------------------------------- 1 | module ssh_password 1.0; 2 | 3 | require { 4 | type sshd_t; 5 | type shadow_t; 6 | class file { read open }; 7 | } 8 | 9 | #============= sshd_t ============== 10 | allow sshd_t shadow_t:file { read open }; 11 | -------------------------------------------------------------------------------- /test/fixtures/vagrantfiles/enforce_selinux.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.provision 'shell', inline: <<-SHELL 3 | yum -y update selinux-* # there are sometimes issues because of old selinux policy in the box 4 | setenforce 1 5 | SHELL 6 | end 7 | -------------------------------------------------------------------------------- /test/integration/default/controls/deprecations.rb: -------------------------------------------------------------------------------- 1 | control 'sshd configuration should not have any deprecations' do 2 | describe command('sshd -t') do 3 | its(:exit_status) { should eq 0 } 4 | its(:stdout) { should eq '' } 5 | its(:stderr) { should eq '' } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Codespell - Spellcheck 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | branches: [master] 7 | pull_request: 8 | branches: [master] 9 | 10 | jobs: 11 | codespell: 12 | uses: "dev-sec/.github/.github/workflows/codespell.yml@main" 13 | with: 14 | ignore_words_list: "te" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | .#* 4 | \#*# 5 | .*.sw[a-z] 6 | *.un~ 7 | coverage 8 | exp.* 9 | pkg/ 10 | shared_test_repo/ 11 | 12 | # Berkshelf 13 | .vagrant 14 | /cookbooks 15 | Berksfile.lock 16 | 17 | # Bundler 18 | Gemfile.lock 19 | bin/* 20 | .bundle/* 21 | 22 | # Test Kitchen 23 | .kitchen/ 24 | .kitchen.local.yml 25 | 26 | # RVM 27 | /.ruby-gemset 28 | /.ruby-version 29 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":gitSignOff" 6 | ], 7 | "dependencyDashboard": true, 8 | "dependencyDashboardAutoclose": true, 9 | "packageRules": [ 10 | { 11 | "matchUpdateTypes": ["patch", "minor"], 12 | "automerge": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/test/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'test' 2 | maintainer 'Dominik Richter' 3 | maintainer_email 'dominik.richter@googlemail.com' 4 | license 'Apache 2.0' 5 | description 'This cookbook is used for testing purposes' 6 | long_description 'This cookbook is used for testing purposes' 7 | version '0.0.1' 8 | 9 | depends 'compat_resource', '>= 12.16.3' 10 | -------------------------------------------------------------------------------- /test/integration/without-pam/ssh_password_selinux_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('getenforce') do 2 | its(:exit_status) { should eq 0 } 3 | its(:stdout) { should include 'Enforcing' } 4 | its(:stderr) { should eq '' } 5 | end 6 | 7 | describe command('semodule -l | grep "ssh_password"') do 8 | its(:exit_status) { should eq 0 } 9 | its(:stdout) { should include 'ssh_password' } 10 | its(:stderr) { should eq '' } 11 | end 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'berkshelf', '~> 8.0' 6 | gem 'chef', '~> 18.0' 7 | 8 | group :test do 9 | gem 'chefspec', '~> 9.3.0' 10 | gem 'coveralls', require: false 11 | gem 'foodcritic', '~> 16.0' 12 | gem 'rake' 13 | gem 'rubocop', '~> 1.82.0' 14 | gem 'simplecov', '~> 0.16' 15 | end 16 | 17 | group :integration do 18 | gem 'kitchen-dokken' 19 | gem 'kitchen-inspec', '~> 2.6.0' 20 | gem 'kitchen-vagrant' 21 | gem 'test-kitchen', '~> 3.0' 22 | end 23 | 24 | group :tools do 25 | gem 'github_changelog_generator', '~> 1.14' 26 | end 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AllCops: 3 | DisplayCopNames: true 4 | Exclude: 5 | - vendor/**/* 6 | - test/**/* 7 | TargetRubyVersion: 2.4 8 | Metrics/AbcSize: 9 | Max: 29 10 | Metrics/CyclomaticComplexity: 11 | Max: 10 12 | Metrics/LineLength: 13 | Enabled: false 14 | Metrics/MethodLength: 15 | Max: 40 16 | Metrics/PerceivedComplexity: 17 | Max: 10 18 | Style/Documentation: 19 | Enabled: false 20 | Layout/DotPosition: 21 | EnforcedStyle: trailing 22 | Enabled: true 23 | Style/Encoding: 24 | EnforcedStyle: always 25 | Enabled: true 26 | Layout/ExtraSpacing: 27 | Exclude: 28 | - attributes/default.rb 29 | Style/RegexpLiteral: 30 | AllowInnerSlashes: true 31 | Layout/SpaceAroundOperators: 32 | Exclude: 33 | - attributes/default.rb 34 | Metrics/BlockLength: 35 | Exclude: 36 | - 'spec/**/*' 37 | Style/FrozenStringLiteralComment: 38 | Enabled: false 39 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Cookbook Name:: ssh-hardening 5 | # Recipe:: default.rb 6 | # 7 | # Copyright 2012, Dominik Richter 8 | # Copyright 2014, Deutsche Telekom AG 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | include_recipe 'ssh-hardening::server' 24 | include_recipe 'ssh-hardening::client' 25 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/test/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Cookbook Name:: test 4 | # Recipe:: default.rb 5 | # 6 | # Copyright 2017, Artem Sidorenko 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | if node['platform_family'] == 'debian' 22 | apt_update 'update-apt-cache' do 23 | action :update 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | bundler_args: "--without development" 4 | cache: bundler 5 | 6 | services: 7 | - docker 8 | 9 | rvm: 2.6.3 10 | 11 | env: 12 | - INSTANCE=ubuntu-1604 13 | - INSTANCE=ubuntu-1604 CHEF_VERSION=14.13.11 14 | - INSTANCE=ubuntu-1804 15 | - INSTANCE=centos-6 16 | - INSTANCE=centos-7 17 | - INSTANCE=centos-7 CHEF_VERSION=14.13.11 18 | - INSTANCE=centos-8 19 | - INSTANCE=oracle-6 20 | - INSTANCE=oracle-7 21 | - INSTANCE=debian-8 22 | - INSTANCE=debian-9 23 | - INSTANCE=debian-10 24 | - INSTANCE=fedora-29 25 | - INSTANCE=fedora-30 26 | - INSTANCE=opensuse-42 27 | - INSTANCE=opensuse-42 CHEF_VERSION=14.13.11 28 | - INSTANCE=amazonlinux-1 29 | - INSTANCE=amazonlinux-2 30 | 31 | script: 32 | - bundle exec rake kitchen KITCHEN_LOCAL_YAML=.kitchen.dokken.yml 33 | 34 | matrix: 35 | include: 36 | - env: UNIT_AND_LINT=1 37 | script: 38 | - bundle exec rake lint spec 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **Expected behavior** 11 | A clear and concise description of what you expected to happen. 12 | 13 | **Actual behavior** 14 | 15 | ```paste below 16 | 17 | ``` 18 | 19 | **Example code** 20 | 21 | ```paste below 22 | 23 | ``` 24 | 25 | **OS / Environment** 26 | 27 | 28 | 29 | **Chef Version** 30 | 31 | ```paste below 32 | 33 | ``` 34 | 35 | **Cookbook Version** 36 | 37 | ```paste below 38 | 39 | ``` 40 | 41 | **Additional context** 42 | Add any other context about the problem here. 43 | -------------------------------------------------------------------------------- /spec/recipes/unlock_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'spec_helper' 20 | 21 | describe 'ssh-hardening::unlock' do 22 | # converge 23 | cached(:chef_run) do 24 | ChefSpec::SoloRunner.new.converge(described_recipe) 25 | end 26 | 27 | # check that the recipes are executed 28 | it 'runs unlock users command' do 29 | expect(chef_run).to run_execute('unlock users').with( 30 | command: "/bin/sed 's/^\\([^:]*:\\)\\!/\\1*/' -i /etc/shadow" 31 | ) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chefspec' 20 | require 'chefspec/berkshelf' 21 | require 'chefspec/cacher' 22 | require 'coveralls' 23 | 24 | # coverage report 25 | Coveralls.wear! 26 | at_exit { ChefSpec::Coverage.report! } 27 | 28 | RSpec.configure do |config| 29 | # OS and version for mocking of ohai data, needed by chefspec 30 | config.platform = 'ubuntu' 31 | config.version = '16.04' 32 | config.file_cache_path = '/tmp/ssh-hardening-file-cache' 33 | end 34 | 35 | require_relative '../libraries/devsec_ssh' 36 | require_relative 'shared_examples_crypto' 37 | -------------------------------------------------------------------------------- /recipes/unlock.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Cookbook Name:: ssh-hardening 5 | # Recipe:: unlock 6 | # 7 | # Copyright 2014, Deutsche Telekom AG 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | # This recipe is only used internally for tests. Do not 23 | # rely on it (for the time being) 24 | 25 | # workaround for unlocking user accounts: 26 | # Locked user accounts are identified via '!' in /etc/shadow 27 | # SSH will deny login to locked accounts, unless UsePAM is active 28 | # To keep UsePAM dactivated, user accounts are 'unlocked', 29 | # but still get an impossible password - so the aim of locking 30 | # is still present, while SSH login is possible. 31 | 32 | execute 'unlock users' do 33 | command "/bin/sed 's/^\\([^:]*:\\)\\!/\\1*/' -i /etc/shadow" 34 | end 35 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | 5 | platforms: 6 | - name: ubuntu-16.04 7 | - name: ubuntu-18.04 8 | - name: centos-6 9 | - name: centos-7 10 | - name: centos-8 11 | - name: oracle-6 12 | - name: oracle-7 13 | - name: debian-8 14 | - name: debian-9 15 | - name: debian-10 16 | - name: fedora-29 17 | - name: fedora-30 18 | - name: opensuse-leap-42 19 | - name: amazonlinux-1 20 | driver_config: 21 | box: realreadme/amazon2016.09 22 | - name: amazonlinux-2 23 | driver_config: 24 | box: stakahashi/amazonlinux2 25 | 26 | provisioner: 27 | name: chef_solo 28 | <% if ENV['CHEF_VERSION'] %> 29 | require_chef_omnibus: <%= ENV['CHEF_VERSION'] %> 30 | <% end %> 31 | chef_license: accept 32 | 33 | verifier: 34 | name: inspec 35 | chef_license: accept 36 | 37 | suites: 38 | - name: default 39 | run_list: 40 | - recipe[test] 41 | - recipe[ssh-hardening] 42 | - name: rhel-with-disabled-pam 43 | includes: 44 | - centos-6 45 | - centos-7 46 | - centos-8 47 | - fedora-29 48 | - fedora-30 49 | - oracle-6 50 | - oracle-7 51 | driver: 52 | provision: true 53 | vagrantfiles: 54 | - test/fixtures/vagrantfiles/enforce_selinux.rb 55 | run_list: 56 | - recipe[test] 57 | - recipe[ssh-hardening] 58 | attributes: 59 | ssh-hardening: 60 | ssh: 61 | server: 62 | use_pam: false 63 | verifier: 64 | inspec_tests: 65 | - test/integration/default 66 | - test/integration/without-pam 67 | -------------------------------------------------------------------------------- /spec/recipes/default_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'spec_helper' 20 | 21 | describe 'ssh-hardening::default' do 22 | # converge 23 | cached(:chef_run) do 24 | ChefSpec::SoloRunner.new.converge(described_recipe) 25 | end 26 | 27 | before do 28 | stub_command("test $(awk '$5 < 2047 && $5 ~ /^[0-9]+$/ { print $5 }' /etc/ssh/moduli | uniq | wc -c) -eq 0").and_return(true) 29 | end 30 | 31 | # check that the recipes are executed 32 | it 'includes server recipe' do 33 | expect(chef_run).to include_recipe('ssh-hardening::server') 34 | end 35 | 36 | it 'includes client recipe' do 37 | expect(chef_run).to include_recipe('ssh-hardening::client') 38 | end 39 | 40 | context 'chef-solo' do 41 | cached(:chef_run) do 42 | ChefSpec::SoloRunner.new.converge(described_recipe) 43 | end 44 | 45 | it 'does not raise an error' do 46 | expect { chef_run }.not_to raise_error 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # to a chef-server or supermarket. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | examples/* 55 | Guardfile 56 | Procfile 57 | .kitchen* 58 | kitchen.yml* 59 | .rubocop.yml 60 | spec/* 61 | Rakefile 62 | .travis.yml 63 | .foodcritic 64 | .codeclimate.yml 65 | 66 | # SCM # 67 | ####### 68 | .git 69 | */.git 70 | .gitignore 71 | .gitmodules 72 | .gitconfig 73 | .gitattributes 74 | .svn 75 | */.bzr/* 76 | */.hg/* 77 | */.svn/* 78 | 79 | # Berkshelf # 80 | ############# 81 | Berksfile 82 | Berksfile.lock 83 | cookbooks/* 84 | tmp 85 | 86 | # Bundler # 87 | ########### 88 | vendor/* 89 | 90 | # Policyfile # 91 | ############## 92 | Policyfile.rb 93 | Policyfile.lock.json 94 | 95 | # Cookbooks # 96 | ############# 97 | CONTRIBUTING* 98 | CHANGELOG* 99 | TESTING* 100 | 101 | # Vagrant # 102 | ########### 103 | .vagrant 104 | Vagrantfile 105 | 106 | # Others # 107 | ########## 108 | .github/* 109 | coverage/* 110 | Gemfile 111 | Gemfile.lock 112 | chefignore 113 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | name 'ssh-hardening' 20 | maintainer 'Dominik Richter' 21 | maintainer_email 'dominik.richter@googlemail.com' 22 | license 'Apache-2.0' 23 | description 'This cookbook installs and provides secure ssh and sshd configurations.' 24 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 25 | version '2.9.0' 26 | 27 | chef_version '>= 12.5' if respond_to?(:chef_version) 28 | 29 | supports 'ubuntu', '>= 12.04' 30 | supports 'debian', '>= 6.0' 31 | supports 'centos', '>= 5.0' 32 | supports 'redhat', '>= 5.0' 33 | supports 'oracle', '>= 6.4' 34 | supports 'fedora', '>= 23.0' 35 | supports 'suse' 36 | supports 'opensuse', '>= 13.2' 37 | supports 'opensuseleap', '>= 42.1' 38 | supports 'amazon' 39 | 40 | recipe 'ssh-hardening::default', 'installs and configures ssh client and server' 41 | recipe 'ssh-hardening::client', 'install and apply security hardening for ssh client' 42 | recipe 'ssh-hardening::server', 'install and apply security hardening for ssh server' 43 | 44 | source_url 'https://github.com/dev-sec/chef-ssh-hardening' 45 | issues_url 'https://github.com/dev-sec/chef-ssh-hardening/issues' 46 | -------------------------------------------------------------------------------- /recipes/client.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Cookbook Name:: ssh-hardening 5 | # Recipe:: client.rb 6 | # 7 | # Copyright 2012, Dominik Richter 8 | # Copyright 2014, Deutsche Telekom AG 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | ohai 'reload openssh-client' do 24 | action :nothing 25 | end 26 | 27 | package 'openssh-client' do 28 | package_name node['ssh-hardening']['sshclient']['package'] 29 | # we need to reload the package version, otherwise we get the version that was installed before cookbook execution 30 | notifies :reload, 'ohai[reload openssh-client]', :immediately 31 | end 32 | 33 | directory 'openssh-client ssh directory /etc/ssh' do 34 | path '/etc/ssh' 35 | mode '0755' 36 | owner 'root' 37 | group 'root' 38 | end 39 | 40 | template '/etc/ssh/ssh_config' do 41 | source 'openssh.conf.erb' 42 | mode '0644' 43 | owner 'root' 44 | group 'root' 45 | variables( 46 | # we do lazy here to ensure we detect the version that comes with the package update above 47 | lazy do 48 | { 49 | mac: node['ssh-hardening']['ssh']['client']['mac'] || DevSec::Ssh.get_client_macs(node['ssh-hardening']['ssh']['client']['weak_hmac']), 50 | kex: node['ssh-hardening']['ssh']['client']['kex'] || DevSec::Ssh.get_client_kexs(node['ssh-hardening']['ssh']['client']['weak_kex']), 51 | cipher: node['ssh-hardening']['ssh']['client']['cipher'] || DevSec::Ssh.get_client_ciphers(node['ssh-hardening']['ssh']['client']['cbc_required']), 52 | version: DevSec::Ssh.get_ssh_client_version 53 | } 54 | end 55 | ) 56 | end 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # rubocop:disable Style/SymbolArray 4 | 5 | require 'foodcritic' 6 | require 'rspec/core/rake_task' 7 | require 'rubocop/rake_task' 8 | require 'chef/cookbook/metadata' 9 | 10 | # General tasks 11 | 12 | # Rubocop before rspec so we don't lint vendored cookbooks 13 | desc 'Run all tests except Kitchen (default task)' 14 | task default: [:lint, :spec] 15 | 16 | # Lint the cookbook 17 | desc 'Run all linters: rubocop and foodcritic' 18 | task lint: [:rubocop, :foodcritic] 19 | 20 | # Run the whole shebang 21 | desc 'Run all tests' 22 | task test: [:lint, :integration, :spec] 23 | 24 | # RSpec 25 | desc 'Run chefspec tests' 26 | task :spec do 27 | puts 'Running Chefspec tests' 28 | RSpec::Core::RakeTask.new(:spec) 29 | end 30 | 31 | # Foodcritic 32 | desc 'Run foodcritic lint checks' 33 | task :foodcritic do 34 | puts 'Running Foodcritic tests...' 35 | FoodCritic::Rake::LintTask.new do |t| 36 | t.options = { fail_tags: ['any'] } 37 | puts 'done.' 38 | end 39 | end 40 | 41 | # Rubocop 42 | desc 'Run Rubocop lint checks' 43 | task :rubocop do 44 | RuboCop::RakeTask.new 45 | end 46 | 47 | # Automatically generate a changelog for this project. Only loaded if 48 | # the necessary gem is installed. 49 | begin 50 | # read version from metadata 51 | metadata = Chef::Cookbook::Metadata.new 52 | metadata.instance_eval(File.read('metadata.rb')) 53 | 54 | # build changelog 55 | require 'github_changelog_generator/task' 56 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 57 | config.future_release = "v#{metadata.version}" 58 | config.user = 'dev-sec' 59 | config.project = 'chef-ssh-hardening' 60 | config.exclude_labels = ['no changelog', 'question', 'duplicate', 'wontfix', 'invalid'] 61 | end 62 | rescue LoadError 63 | puts '>>>>> GitHub Changelog Generator not loaded, omitting tasks' 64 | end 65 | 66 | desc 'Run kitchen integration tests' 67 | task :kitchen do 68 | concurrency = ENV['CONCURRENCY'] || 1 69 | instance = ENV['INSTANCE'] || '' 70 | args = ENV['CI'] ? '--destroy=always' : '' 71 | sh('sh', '-c', "bundle exec kitchen test -c #{concurrency} #{args} #{instance}") 72 | end 73 | -------------------------------------------------------------------------------- /.kitchen.dokken.yml: -------------------------------------------------------------------------------- 1 | # this file is used for configuration of kitchen dokken 2 | # for integration tests in the CI 3 | --- 4 | driver: 5 | name: dokken 6 | privileged: true # because Docker and SystemD/Upstart 7 | <% if ENV['CHEF_VERSION'] %> 8 | chef_version: <%= ENV['CHEF_VERSION'] %> 9 | <% end %> 10 | 11 | transport: 12 | name: dokken 13 | 14 | provisioner: 15 | name: dokken 16 | 17 | verifier: 18 | name: inspec 19 | sudo: true 20 | 21 | platforms: 22 | - name: amazonlinux-1 23 | driver: 24 | image: dokken/amazonlinux 25 | pid_one_command: /sbin/init 26 | - name: amazonlinux-2 27 | driver: 28 | image: dokken/amazonlinux-2 29 | pid_one_command: /usr/lib/systemd/systemd 30 | - name: centos-6 31 | driver: 32 | image: dokken/centos-6 33 | pid_one_command: /sbin/init 34 | - name: centos-7 35 | driver: 36 | image: dokken/centos-7 37 | pid_one_command: /usr/lib/systemd/systemd 38 | - name: centos-8 39 | driver: 40 | image: dokken/centos-8 41 | pid_one_command: /usr/lib/systemd/systemd 42 | - name: oracle-6 43 | driver: 44 | image: oraclelinux:6 45 | - name: oracle-7 46 | driver: 47 | image: oraclelinux:7 48 | pid_one_command: /usr/lib/systemd/systemd 49 | - name: debian-8 50 | driver: 51 | image: dokken/debian-8 52 | intermediate_instructions: 53 | - RUN /usr/bin/apt-get update 54 | pid_one_command: /bin/systemd 55 | - name: debian-9 56 | driver: 57 | image: dokken/debian-9 58 | intermediate_instructions: 59 | - RUN /usr/bin/apt-get update 60 | pid_one_command: /bin/systemd 61 | - name: debian-10 62 | driver: 63 | image: dokken/debian-10 64 | intermediate_instructions: 65 | - RUN /usr/bin/apt-get update 66 | pid_one_command: /bin/systemd 67 | - name: fedora-29 68 | driver: 69 | image: dokken/fedora-29 70 | pid_one_command: /usr/lib/systemd/systemd 71 | intermediate_instructions: 72 | - RUN dnf install -y yum 73 | - name: fedora-30 74 | driver: 75 | image: dokken/fedora-30 76 | pid_one_command: /usr/lib/systemd/systemd 77 | intermediate_instructions: 78 | - RUN dnf install -y yum 79 | - name: opensuse-42 80 | driver: 81 | image: dokken/opensuse-leap 82 | pid_one_command: /bin/systemd 83 | - name: ubuntu-16.04 84 | driver: 85 | image: dokken/ubuntu-16.04 86 | pid_one_command: /bin/systemd 87 | intermediate_instructions: 88 | - RUN /usr/bin/apt-get update 89 | - name: ubuntu-18.04 90 | driver: 91 | image: dokken/ubuntu-18.04 92 | pid_one_command: /bin/systemd 93 | intermediate_instructions: 94 | - RUN /usr/bin/apt-get update 95 | 96 | suites: 97 | - name: default 98 | run_list: 99 | - recipe[test] 100 | - recipe[ssh-hardening] 101 | -------------------------------------------------------------------------------- /spec/shared_examples_crypto.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # Copyright 2016, Artem Sidorenko 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | RSpec.shared_examples 'does not allow weak hmacs' do 21 | it 'should not allow weak hmacs' do 22 | helper_lib::CRYPTO[:macs][:weak].each do |mac| 23 | expect(chef_run).not_to render_file(ssh_config_file). 24 | with_content(/MACs [^#]*\b#{mac}\b/) 25 | end 26 | end 27 | end 28 | 29 | RSpec.shared_examples 'does not allow weak kexs' do 30 | it 'should not allow weak kexs' do 31 | helper_lib::CRYPTO[:kexs][:weak].each do |kex| 32 | expect(chef_run).not_to render_file(ssh_config_file). 33 | with_content(/KexAlgorithms [^#]*\b#{kex}\b/) 34 | end 35 | end 36 | end 37 | 38 | RSpec.shared_examples 'does not allow weak ciphers' do 39 | it 'should not allow weak ciphers' do 40 | helper_lib::CRYPTO[:ciphers][:weak].each do |cipher| 41 | expect(chef_run).not_to render_file(ssh_config_file). 42 | with_content(/Ciphers [^#]*\b#{cipher}\b/) 43 | end 44 | end 45 | end 46 | 47 | RSpec.shared_examples 'allow ctr ciphers' do 48 | let(:ctr_ciphers) { %w[aes256-ctr aes192-ctr aes128-ctr] } 49 | it 'should allow ctr ciphers' do 50 | ctr_ciphers.each do |cipher| 51 | expect(chef_run).to render_file(ssh_config_file). 52 | with_content(/Ciphers [^#]*\b#{cipher}\b/) 53 | end 54 | end 55 | end 56 | 57 | RSpec.shared_examples 'allow weak hmacs' do 58 | it 'should allow weak hmacs' do 59 | helper_lib::CRYPTO[:macs][:weak].each do |mac| 60 | expect(chef_run).to render_file(ssh_config_file). 61 | with_content(/MACs [^#]*\b#{mac}\b/) 62 | end 63 | end 64 | end 65 | 66 | RSpec.shared_examples 'allow weak kexs' do 67 | it 'should allow weak kexs' do 68 | helper_lib::CRYPTO[:kexs][:weak].each do |kex| 69 | expect(chef_run).to render_file(ssh_config_file). 70 | with_content(/KexAlgorithms [^#]*\b#{kex}\b/) 71 | end 72 | end 73 | end 74 | 75 | RSpec.shared_examples 'allow weak ciphers' do 76 | it 'should allow weak ciphers' do 77 | helper_lib::CRYPTO[:ciphers][:weak].each do |cipher| 78 | expect(chef_run).to render_file(ssh_config_file). 79 | with_content(/Ciphers [^#]*\b#{cipher}\b/) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guideline 2 | 3 | This document provides an overview of how you can participat in improving this project or extending it. We are grateful for all your help: bug reports and fixes, code contributions, documentation or ideas. Feel free to join, we appreciate your support!! 4 | 5 | ## Communication 6 | 7 | ### GitHub repositories 8 | 9 | Much of the issues, goals and ideas are tracked in the respective projects in GitHub. Please use this channel to report bugs and post ideas. 10 | 11 | ### Github projects 12 | 13 | The overall hardening project is organized publicly with Github projects. Feel free to add tasks and ideas for the overall project. In most cases, those tasks should match a specific issue located in this repository. [https://github.com/orgs/dev-sec/projects](https://github.com/orgs/dev-sec/projects) 14 | 15 | ## git and GitHub 16 | 17 | In order to contribute code please: 18 | 19 | 1. Fork the project on GitHub 20 | 2. Clone the project 21 | 3. Add changes (and tests) 22 | 4. Commit and push 23 | 5. Create a merge-request 24 | 25 | To have your code merged, see the expectations listed below. 26 | 27 | You can find a well-written guide [here](https://help.github.com/articles/fork-a-repo). 28 | 29 | Please follow common commit best-practices. Be explicit, have a short summary, a well-written description and references. This is especially important for the merge-request. 30 | 31 | Some great guidelines can be found [here](https://wiki.openstack.org/wiki/GitCommitMessages) and [here](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message). 32 | 33 | 34 | ## Expectations 35 | 36 | ### Don't reinvent the wheel 37 | 38 | This hardening project doesn't intend to reinvent the configuration stack for services. Aim to use official configuration projects first and provide hardening as a layer on top. The goal is remove the need for a user to configure all aspects of services and maintain security configuration. This way, the user can still configure a service using the interface provided by the official project. 39 | 40 | * For Chef refer to the official [opscode community cookbooks](http://community.opscode.com/cookbooks). 41 | * For Puppet head to the [Puppet Forge](https://forge.puppetlabs.com/) and take a node of the Puppet supported modules. 42 | 43 | These projects are generally hosted on GitHub as well. 44 | 45 | In some cases, we in fact create the full rollout stack, but this is generally the exception ([os-hardening](https://github.com/TelekomLabs/chef-os-hardening), [ssh-hardening](https://github.com/TelekomLabs/chef-ssh-hardening)). 46 | 47 | 48 | ### Be explicit 49 | 50 | * Please avoid using nonsensical property and variable names 51 | * Use self-describing attribute names for user configuration 52 | * In case of failures, communicate what happened and why a failure occurs to the user. Make it easy to track the code or action that produced the error. Try to catch and handle errors if possible to provide improved failure messages. 53 | 54 | 55 | ### Add tests 56 | 57 | The security review of this project is done using integration tests. 58 | 59 | Whenever you add a new security configuration, please start by writing a test that checks for this configuration. For example: If you want to set a new attribute in a configuration file, write a test that expects the value to be set first. Then implement your change. 60 | 61 | You may add a new feature request by creating a test for whatever value you need. 62 | 63 | All tests will be reviewed internally for their validity and overall project direction. 64 | 65 | 66 | ### Document your code 67 | 68 | As code is more often read than written, please provide documentation in all projects. 69 | 70 | Adhere to the respective guidelines for documentation: 71 | 72 | * Chef generally documents code based explicit readme files. For code documentation please use [yard-chef](https://github.com/rightscale/yard-chef) 73 | * [Puppet module documentation](http://docs.puppetlabs.com/puppet/latest/reference/modules_documentation.html) 74 | 75 | 76 | ### Follow coding styles 77 | 78 | We generally include test for coding guidelines: 79 | 80 | * Chef follows [Foodcritic](http://acrmp.github.io/foodcritic/) 81 | * Puppet is checked with [puppet-lint](http://puppet-lint.com/checks/) 82 | 83 | Remember: Code is generally read much more often than written. 84 | 85 | 86 | ### Use Markdown 87 | 88 | Wherever possible, please refrain from any other formats and stick to simple markdown. 89 | -------------------------------------------------------------------------------- /templates/default/openssh.conf.erb: -------------------------------------------------------------------------------- 1 | <% node['ssh-hardening']['config_disclaimer'].to_s.split("\n").each do |l| %> 2 | # <%= l %> 3 | <% end %> 4 | #--- 5 | 6 | # This is the ssh client system-wide configuration file. 7 | # See ssh_config(5) for more information on any settings used. Comments will be added only to clarify why a configuration was chosen. 8 | # 9 | # Created for OpenSSH v5.9 up to 6.8 10 | 11 | # Basic configuration 12 | # =================== 13 | 14 | # Address family should always be limited to the active network configuration. 15 | AddressFamily <%= ((@node['ssh-hardening']['network']['ipv6']['enable']) ? "any" : "inet" ) %> 16 | 17 | <% Array(@node['ssh-hardening']['ssh']['client']['remote_hosts']).each do |host| %> 18 | # Restrict the following configuration to be limited to this Host. 19 | Host <%= host %> 20 | <% end %> 21 | 22 | <% Array(@node['ssh-hardening']['ssh']['ports']).each do |ssh_port| %> 23 | # The port at the destination should be defined 24 | Port <%= ssh_port %> 25 | <% end %> 26 | 27 | # Identity file configuration. You may restrict available identity files. Otherwise ssh will search for a pattern and use any that matches. 28 | #IdentityFile ~/.ssh/identity 29 | #IdentityFile ~/.ssh/id_rsa 30 | #IdentityFile ~/.ssh/id_dsa 31 | 32 | 33 | # Security configuration 34 | # ====================== 35 | 36 | # Set the protocol version to 2 for security reasons. Disables legacy support. 37 | Protocol 2 38 | 39 | # Make sure passphrase querying is enabled 40 | BatchMode no 41 | 42 | # Prevent IP spoofing by checking to host IP against the `known_hosts` file. 43 | CheckHostIP yes 44 | 45 | # Always ask before adding keys to the `known_hosts` file. Do not set to `yes`. 46 | StrictHostKeyChecking ask 47 | 48 | # **Ciphers** -- If your clients don't support CTR (eg older versions), cbc will be added 49 | # CBC: is true if you want to connect with OpenSSL-base libraries 50 | # eg ruby Net::SSH::Transport::CipherFactory requires cbc-versions of the given openssh ciphers to work 51 | # -- see: (http://net-ssh.github.com/net-ssh/classes/Net/SSH/Transport/CipherFactory.html) 52 | # 53 | <% if @cipher %> 54 | Ciphers <%= @cipher %> 55 | <% end %> 56 | 57 | # **Hash algorithms** -- Make sure not to use SHA1 for hashing, unless it is really necessary. 58 | # Weak HMAC is sometimes required if older package versions are used 59 | # eg Ruby's Net::SSH at around 2.2.* doesn't support sha2 for hmac, so this will have to be set true in this case. 60 | # 61 | <% if @mac %> 62 | MACs <%= @mac %> 63 | <% end %> 64 | 65 | # Alternative setting, if OpenSSH version is below v5.9 66 | #MACs hmac-ripemd160 67 | 68 | # **Key Exchange Algorithms** -- Make sure not to use SHA1 for kex, unless it is really necessary 69 | # Weak kex is sometimes required if older package versions are used 70 | # eg ruby's Net::SSH at around 2.2.* doesn't support sha2 for kex, so this will have to be set true in this case. 71 | # 72 | <% if @kex %> 73 | KexAlgorithms <%= @kex %> 74 | <% end %> 75 | 76 | 77 | # Disable agent forwarding, since local agent could be accessed through forwarded connection. 78 | ForwardAgent <%= ((@node['ssh-hardening']['ssh']['client']['allow_agent_forwarding']) ? 'yes' : 'no' ) %> 79 | 80 | # Disable X11 forwarding, since local X11 display could be accessed through forwarded connection. 81 | ForwardX11 no 82 | 83 | # Never use host-based authentication. It can be exploited. 84 | HostbasedAuthentication no 85 | 86 | <% if @version.to_f < 7.6 %> 87 | RhostsRSAAuthentication no 88 | 89 | # Enable RSA authentication via identity files. 90 | RSAAuthentication yes 91 | <% end %> 92 | 93 | # Disable password-based authentication, it can allow for potentially easier brute-force attacks. 94 | PasswordAuthentication <%= ((@node['ssh-hardening']['ssh']['client']['password_authentication']) ? "yes" : "no" ) %> 95 | 96 | # Only use GSSAPIAuthentication if implemented on the network. 97 | GSSAPIAuthentication no 98 | GSSAPIDelegateCredentials no 99 | 100 | # Disable tunneling 101 | Tunnel no 102 | 103 | # Disable local command execution. 104 | PermitLocalCommand no 105 | 106 | 107 | # Misc. configuration 108 | # =================== 109 | 110 | # Enable compression. More pressure on the CPU, less on the network. 111 | Compression yes 112 | 113 | #EscapeChar ~ 114 | #VisualHostKey yes 115 | 116 | # http://undeadly.org/cgi?action=article&sid=20160114142733 117 | UseRoaming <%= @node['ssh-hardening']['ssh']['client']['roaming'] ? 'yes' : 'no' %> 118 | 119 | <% unless @node['ssh-hardening']['ssh']['client']['send_env'].empty? %> 120 | # Send locale environment variables 121 | SendEnv <%= @node['ssh-hardening']['ssh']['client']['send_env'].join(' ') %> 122 | <% end %> 123 | 124 | <%- unless @node['ssh-hardening']['ssh']['client']['extras'].empty? %> 125 | # Extra Configuration Options 126 | <%- @node['ssh-hardening']['ssh']['client']['extras'].each do |key, value| %> 127 | <%= key %> <%= value %> 128 | <% end -%> 129 | <% end -%> 130 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Cookbook Name:: ssh-hardening 5 | # Attributes:: default 6 | # 7 | # Copyright 2012, Dominik Richter 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | # Define the client package name 23 | case node['platform'] 24 | when 'redhat', 'centos', 'fedora', 'amazon', 'oracle', 'scientific', 'rocky', 'almalinux' 25 | default['ssh-hardening']['sshclient']['package'] = 'openssh-clients' 26 | when 'debian', 'ubuntu' 27 | default['ssh-hardening']['sshclient']['package'] = 'openssh-client' 28 | when 'arch', 'suse', 'opensuse', 'opensuseleap' 29 | default['ssh-hardening']['sshclient']['package'] = 'openssh' 30 | else 31 | default['ssh-hardening']['sshclient']['package'] = 'openssh-client' 32 | end 33 | 34 | # Define the package name for selinux utils 35 | if node['platform_family'] == 'fedora' || # rubocop:disable Style/ConditionalAssignment 36 | node['platform_family'] == 'rhel' && node['platform_version'].to_f >= 8 37 | default['ssh-hardening']['selinux']['package'] = 'policycoreutils-python-utils' 38 | else 39 | default['ssh-hardening']['selinux']['package'] = 'policycoreutils-python' 40 | end 41 | 42 | # Define the server package name 43 | case node['platform'] 44 | when 'suse', 'opensuse', 'opensuseleap' 45 | default['ssh-hardening']['sshserver']['package'] = 'openssh' 46 | else 47 | default['ssh-hardening']['sshserver']['package'] = 'openssh-server' 48 | end 49 | 50 | # Define the service name for sshd 51 | case node['platform_family'] 52 | when 'rhel', 'fedora', 'suse', 'freebsd', 'gentoo', 'amazon' 53 | default['ssh-hardening']['sshserver']['service_name'] = 'sshd' 54 | else 55 | default['ssh-hardening']['sshserver']['service_name'] = 'ssh' 56 | end 57 | 58 | # sshd + ssh client 59 | default['ssh-hardening']['network']['ipv6']['enable'] = false 60 | default['ssh-hardening']['config_disclaimer'] = '**Note:** This file was automatically created by Hardening Framework (dev-sec.io) configuration. If you use its automated setup, do not edit this file directly, but adjust the automation instead.' 61 | default['ssh-hardening']['ssh']['ports'] = [22] 62 | 63 | # ssh client 64 | default['ssh-hardening']['ssh']['client'].tap do |client| 65 | client['mac'] = nil # nil = calculate best combination for client 66 | client['kex'] = nil # nil = calculate best combination for client 67 | client['cipher'] = nil # nil = calculate best combination for client 68 | client['cbc_required'] = false 69 | client['weak_hmac'] = false 70 | client['weak_kex'] = false 71 | client['allow_agent_forwarding'] = false 72 | client['remote_hosts'] = [] 73 | client['password_authentication'] = false # ssh 74 | # http://undeadly.org/cgi?action=article&sid=20160114142733 75 | client['roaming'] = false 76 | client['send_env'] = ['LANG', 'LC_*', 'LANGUAGE'] 77 | 78 | # extra client configuration options 79 | client['extras'] = {} 80 | end 81 | 82 | # sshd 83 | default['ssh-hardening']['ssh']['server'].tap do |server| # rubocop: disable BlockLength 84 | server['kex'] = nil # nil = calculate best combination for server version 85 | server['cipher'] = nil # nil = calculate best combination for server version 86 | server['mac'] = nil # nil = calculate best combination for server version 87 | server['cbc_required'] = false 88 | server['weak_hmac'] = false 89 | server['weak_kex'] = false 90 | server['dh_min_prime_size'] = 2048 91 | server['dh_build_primes'] = false 92 | server['dh_build_primes_size'] = 4096 93 | server['host_key_files'] = nil 94 | server['client_alive_interval'] = 300 # 5min 95 | server['client_alive_count'] = 3 # ~> 3 x interval 96 | server['allow_root_with_key'] = false 97 | server['permit_tunnel'] = false 98 | server['allow_tcp_forwarding'] = false 99 | server['allow_agent_forwarding'] = false 100 | server['allow_x11_forwarding'] = false 101 | server['use_pam'] = true 102 | server['challenge_response_authentication'] = false 103 | server['deny_users'] = [] 104 | server['allow_users'] = [] 105 | server['deny_groups'] = [] 106 | server['allow_groups'] = [] 107 | server['print_motd'] = false 108 | server['print_last_log'] = false 109 | server['banner'] = nil # set this to nil to disable banner or provide a path like '/etc/issue.net' 110 | server['os_banner'] = false # (Debian OS family) 111 | server['use_dns'] = nil # set this to nil to let us use the default OpenSSH in case it's not set by the user 112 | server['use_privilege_separation'] = nil # set this to nil to let us detect the attribute based on the node platform 113 | server['login_grace_time'] = '30s' 114 | server['max_auth_tries'] = 2 115 | server['max_sessions'] = 10 116 | server['max_startups'] = '10:30:100' 117 | server['password_authentication'] = false 118 | server['log_level'] = 'verbose' 119 | server['accept_env'] = ['LANG', 'LC_*', 'LANGUAGE'] 120 | server['authorized_keys_path'] = nil # if not nil, full path to one or multiple space-separated authorized keys file is expected 121 | 122 | # extra server configuration options 123 | server['extras'] = {} 124 | 125 | # server match configuration block 126 | server['match_blocks'] = {} 127 | 128 | # sshd sftp options 129 | server['sftp']['enable'] = false 130 | server['sftp']['log_level'] = 'VERBOSE' 131 | server['sftp']['group'] = 'sftponly' 132 | server['sftp']['chroot'] = '/home/%u' 133 | server['sftp']['authorized_keys_path'] = nil # if not nil, full path to one or multiple space-separated authorized keys file is expected 134 | server['sftp']['password_authentication'] = false 135 | end 136 | -------------------------------------------------------------------------------- /recipes/server.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Cookbook Name:: ssh-hardening 5 | # Recipe:: server.rb 6 | # 7 | # Copyright 2012, Dominik Richter 8 | # Copyright 2014, Deutsche Telekom AG 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | # default attributes 24 | # We can not set this kind of defaults in the attribute files 25 | # as we react on value of other attributes 26 | # https://github.com/dev-sec/chef-ssh-hardening/issues/140#issuecomment-267779720 27 | node.default['ssh-hardening']['ssh']['server']['listen_to'] = 28 | if node['ssh-hardening']['network']['ipv6']['enable'] 29 | ['0.0.0.0', '::'] 30 | else 31 | ['0.0.0.0'] 32 | end 33 | 34 | # some internal definitions 35 | cache_dir = ::File.join(Chef::Config[:file_cache_path], cookbook_name.to_s) 36 | dh_moduli_file = '/etc/ssh/moduli' 37 | 38 | # create a cache dir for this cookbook 39 | # we use it for storing of lock files or selinux files 40 | directory cache_dir 41 | 42 | # installs package name 43 | ohai 'reload openssh-server' do 44 | action :nothing 45 | end 46 | 47 | package 'openssh-server' do 48 | package_name node['ssh-hardening']['sshserver']['package'] 49 | # we need to reload the package version, otherwise we get the version that was installed before cookbook execution 50 | notifies :reload, 'ohai[reload openssh-server]', :immediately 51 | end 52 | 53 | # Handle additional SELinux policy on RHEL/Fedora for different UsePAM options 54 | if %w[fedora rhel].include?(node['platform_family']) 55 | policy_file = ::File.join(cache_dir, 'ssh_password.te') 56 | module_file = ::File.join(cache_dir, 'ssh_password.mod') 57 | package_file = ::File.join(cache_dir, 'ssh_password.pp') 58 | 59 | package node['ssh-hardening']['selinux']['package'] 60 | 61 | if node['ssh-hardening']['ssh']['server']['use_pam'] 62 | # UsePAM yes: disable and remove the additional SELinux policy 63 | 64 | execute 'remove selinux policy' do 65 | command 'semodule -r ssh_password' 66 | only_if 'getenforce | grep -vq Disabled && semodule -l | grep -q ssh_password' 67 | end 68 | else 69 | # UsePAM no: enable and install the additional SELinux policy 70 | 71 | cookbook_file policy_file do 72 | source 'ssh_password.te' 73 | end 74 | 75 | bash 'build selinux package and install it' do 76 | code <<-EOC 77 | checkmodule -M -m -o #{module_file} #{policy_file} 78 | semodule_package -o #{package_file} -m #{module_file} 79 | semodule -i #{package_file} 80 | EOC 81 | not_if 'getenforce | grep -q Disabled || semodule -l | grep -q ssh_password' 82 | end 83 | end 84 | end 85 | 86 | # handle Diffie-Hellman moduli 87 | # build own moduli file if required 88 | own_primes_lock_file = ::File.join(cache_dir, 'moduli.lock') 89 | bash 'build own primes for DH' do 90 | code <<-EOS 91 | set -e 92 | tempdir=$(mktemp -d) 93 | ssh-keygen -G $tempdir/moduli.all -b #{node['ssh-hardening']['ssh']['server']['dh_build_primes_size']} 94 | ssh-keygen -T $tempdir/moduli.safe -f $tempdir/moduli.all 95 | cp $tempdir/moduli.safe #{dh_moduli_file} 96 | rm -rf $tempdir 97 | touch #{own_primes_lock_file} 98 | EOS 99 | only_if { node['ssh-hardening']['ssh']['server']['dh_build_primes'] } 100 | not_if { ::File.exist?(own_primes_lock_file) } 101 | notifies :restart, 'service[sshd]' 102 | end 103 | 104 | # remove all small primes 105 | # https://stribika.github.io/2015/01/04/secure-secure-shell.html 106 | dh_min_prime_size = node['ssh-hardening']['ssh']['server']['dh_min_prime_size'].to_i - 1 # 4096 is 4095 in the moduli file 107 | ruby_block 'remove small primes from DH moduli' do # ~FC014 108 | block do 109 | tmp_file = "#{dh_moduli_file}.tmp" 110 | ::File.open(tmp_file, 'w') do |new_file| 111 | ::File.readlines(dh_moduli_file).each do |line| 112 | unless line_match = line.match(/^(\d+ ){4}(\d+) \d+ \h+$/) # rubocop:disable Lint/AssignmentInCondition 113 | # some line without expected data structure, e.g. comment line 114 | # write it and go to the next data 115 | new_file.write(line) 116 | next 117 | end 118 | 119 | # lets compare the bits and do not write the lines with small bit size 120 | bits = line_match[2] 121 | new_file.write(line) unless bits.to_i < dh_min_prime_size 122 | end 123 | end 124 | 125 | # we use cp&rm to preserve the permissions of existing file 126 | FileUtils.cp(tmp_file, dh_moduli_file) 127 | FileUtils.rm(tmp_file) 128 | end 129 | not_if "test $(awk '$5 < #{dh_min_prime_size} && $5 ~ /^[0-9]+$/ { print $5 }' #{dh_moduli_file} | uniq | wc -c) -eq 0" 130 | notifies :restart, 'service[sshd]' 131 | end 132 | 133 | # defines the sshd service 134 | service 'sshd' do 135 | # use upstart for ubuntu, otherwise chef uses init 136 | # @see http://docs.opscode.com/resource_service.html#providers 137 | case node['platform'] 138 | when 'ubuntu' 139 | if node['platform_version'].to_f >= 15.04 140 | provider Chef::Provider::Service::Systemd 141 | elsif node['platform_version'].to_f >= 12.04 142 | provider Chef::Provider::Service::Upstart 143 | end 144 | end 145 | service_name node['ssh-hardening']['sshserver']['service_name'] 146 | supports value_for_platform( 147 | 'centos' => { 'default' => %i[restart reload status] }, 148 | 'redhat' => { 'default' => %i[restart reload status] }, 149 | 'fedora' => { 'default' => %i[restart reload status] }, 150 | 'scientific' => { 'default' => %i[restart reload status] }, 151 | 'arch' => { 'default' => [:restart] }, 152 | 'debian' => { 'default' => %i[restart reload status] }, 153 | 'ubuntu' => { 154 | '8.04' => %i[restart reload], 155 | 'default' => %i[restart reload status] 156 | }, 157 | 'default' => { 'default' => %i[restart reload] } 158 | ) 159 | action %i[enable start] 160 | end 161 | 162 | directory 'openssh-server ssh directory /etc/ssh' do 163 | path '/etc/ssh' 164 | mode '0755' 165 | owner 'root' 166 | group 'root' 167 | end 168 | 169 | template '/etc/ssh/sshd_config' do 170 | source 'opensshd.conf.erb' 171 | mode '0600' 172 | owner 'root' 173 | group 'root' 174 | variables( 175 | # we do lazy here to ensure we detect the version that comes with the package update above 176 | lazy do 177 | { 178 | permit_tunnel: DevSec::Ssh.validate_permit_tunnel(node['ssh-hardening']['ssh']['server']['permit_tunnel']), 179 | mac: node['ssh-hardening']['ssh']['server']['mac'] || DevSec::Ssh.get_server_macs(node['ssh-hardening']['ssh']['server']['weak_hmac']), 180 | kex: node['ssh-hardening']['ssh']['server']['kex'] || DevSec::Ssh.get_server_kexs(node['ssh-hardening']['ssh']['server']['weak_kex']), 181 | cipher: node['ssh-hardening']['ssh']['server']['cipher'] || DevSec::Ssh.get_server_ciphers(node['ssh-hardening']['ssh']['server']['cbc_required']), 182 | use_priv_sep: node['ssh-hardening']['ssh']['use_privilege_separation'] || DevSec::Ssh.get_server_privilege_separarion, 183 | hostkeys: node['ssh-hardening']['ssh']['server']['host_key_files'] || DevSec::Ssh.get_server_algorithms.map { |alg| "/etc/ssh/ssh_host_#{alg}_key" }, 184 | version: DevSec::Ssh.get_ssh_server_version 185 | } 186 | end 187 | ) 188 | notifies :restart, 'service[sshd]' 189 | end 190 | 191 | execute 'unlock root account if it is locked' do 192 | command "sed 's/^root:\!/root:*/' /etc/shadow -i" 193 | only_if { node['ssh-hardening']['ssh']['allow_root_with_key'] } 194 | end 195 | -------------------------------------------------------------------------------- /spec/recipes/client_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # Copyright 2016, Artem Sidorenko 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe 'ssh-hardening::client' do 23 | let(:helper_lib) { DevSec::Ssh } 24 | let(:ssh_config_file) { '/etc/ssh/ssh_config' } 25 | 26 | # converge 27 | cached(:chef_run) do 28 | ChefSpec::SoloRunner.new.converge(described_recipe) 29 | end 30 | 31 | it 'installs openssh-client' do 32 | expect(chef_run).to install_package('openssh-client').with( 33 | package_name: 'openssh-client' 34 | ) 35 | end 36 | 37 | it 'creates the directory /etc/ssh' do 38 | expect(chef_run).to create_directory('/etc/ssh').with( 39 | mode: '0755', 40 | owner: 'root', 41 | group: 'root' 42 | ) 43 | end 44 | 45 | it 'creates /etc/ssh/ssh_config' do 46 | expect(chef_run).to create_template('/etc/ssh/ssh_config').with( 47 | source: 'openssh.conf.erb', 48 | mode: '0644', 49 | owner: 'root', 50 | group: 'root' 51 | ) 52 | end 53 | 54 | include_examples 'does not allow weak hmacs' 55 | include_examples 'does not allow weak kexs' 56 | include_examples 'does not allow weak ciphers' 57 | 58 | it 'disables client roaming' do 59 | expect(chef_run).to render_file('/etc/ssh/ssh_config'). 60 | with_content(/UseRoaming no/) 61 | end 62 | 63 | it 'sends default locale environment variables' do 64 | expect(chef_run).to render_file('/etc/ssh/ssh_config'). 65 | with_content('SendEnv LANG LC_* LANGUAGE') 66 | end 67 | 68 | include_examples 'allow ctr ciphers' 69 | 70 | context 'weak_hmac enabled only for the client' do 71 | cached(:chef_run) do 72 | ChefSpec::SoloRunner.new do |node| 73 | node.normal['ssh-hardening']['ssh']['client']['weak_hmac'] = true 74 | end.converge(described_recipe) 75 | end 76 | 77 | include_examples 'allow weak hmacs' 78 | end 79 | 80 | context 'weak_hmac enabled only for the server' do 81 | cached(:chef_run) do 82 | ChefSpec::SoloRunner.new do |node| 83 | node.normal['ssh-hardening']['ssh']['server']['weak_hmac'] = true 84 | end.converge(described_recipe) 85 | end 86 | 87 | include_examples 'does not allow weak hmacs' 88 | end 89 | 90 | context 'weak_kex enabled for the client only' do 91 | cached(:chef_run) do 92 | ChefSpec::SoloRunner.new do |node| 93 | node.normal['ssh-hardening']['ssh']['client']['weak_kex'] = true 94 | end.converge(described_recipe) 95 | end 96 | 97 | include_examples 'allow weak kexs' 98 | end 99 | 100 | context 'weak_kexs enabled for the server only' do 101 | cached(:chef_run) do 102 | ChefSpec::SoloRunner.new do |node| 103 | node.normal['ssh-hardening']['ssh']['server']['weak_kex'] = true 104 | end.converge(described_recipe) 105 | end 106 | 107 | include_examples 'does not allow weak kexs' 108 | end 109 | 110 | context 'cbc_required set for the client only' do 111 | cached(:chef_run) do 112 | ChefSpec::SoloRunner.new do |node| 113 | node.normal['ssh-hardening']['ssh']['client']['cbc_required'] = true 114 | end.converge(described_recipe) 115 | end 116 | 117 | include_examples 'allow weak ciphers' 118 | end 119 | 120 | context 'cbc_required set for the server only' do 121 | cached(:chef_run) do 122 | ChefSpec::SoloRunner.new do |node| 123 | node.normal['ssh-hardening']['ssh']['server']['cbc_required'] = true 124 | end.converge(described_recipe) 125 | end 126 | 127 | include_examples 'does not allow weak ciphers' 128 | end 129 | 130 | context 'with custom KEXs' do 131 | cached(:chef_run) do 132 | ChefSpec::SoloRunner.new do |node| 133 | node.normal['ssh-hardening']['ssh']['client']['kex'] = 'mycustomkexvalue' 134 | end.converge(described_recipe) 135 | end 136 | 137 | it 'uses the value of kex attribute' do 138 | expect(chef_run).to render_file('/etc/ssh/ssh_config'). 139 | with_content(/KexAlgorithms mycustomkexvalue/) 140 | end 141 | end 142 | 143 | context 'with custom MACs' do 144 | cached(:chef_run) do 145 | ChefSpec::SoloRunner.new do |node| 146 | node.normal['ssh-hardening']['ssh']['client']['mac'] = 'mycustommacvalue' 147 | end.converge(described_recipe) 148 | end 149 | 150 | it 'uses the value of mac attribute' do 151 | expect(chef_run).to render_file('/etc/ssh/ssh_config'). 152 | with_content(/MACs mycustommacvalue/) 153 | end 154 | end 155 | 156 | context 'with custom ciphers' do 157 | cached(:chef_run) do 158 | ChefSpec::SoloRunner.new do |node| 159 | node.normal['ssh-hardening']['ssh']['client']['cipher'] = 'mycustomciphervalue' 160 | end.converge(described_recipe) 161 | end 162 | 163 | it 'uses the value of cipher attribute' do 164 | expect(chef_run).to render_file('/etc/ssh/ssh_config'). 165 | with_content(/Ciphers mycustomciphervalue/) 166 | end 167 | end 168 | 169 | context 'with empty send_env attribute' do 170 | cached(:chef_run) do 171 | ChefSpec::SoloRunner.new do |node| 172 | node.normal['ssh-hardening']['ssh']['client']['send_env'] = [] 173 | end.converge(described_recipe) 174 | end 175 | 176 | it 'will not send any environment variables' do 177 | expect(chef_run).to_not render_file('/etc/ssh/ssh_config'). 178 | with_content(/SendEnv/) 179 | end 180 | end 181 | 182 | context 'with custom send_env attribute' do 183 | cached(:chef_run) do 184 | ChefSpec::SoloRunner.new do |node| 185 | node.normal['ssh-hardening']['ssh']['client']['send_env'] = %w[some environment variables] 186 | end.converge(described_recipe) 187 | end 188 | 189 | it 'uses the value of send_env attribute' do 190 | expect(chef_run).to render_file('/etc/ssh/ssh_config'). 191 | with_content(/SendEnv some environment variables/) 192 | end 193 | end 194 | 195 | describe 'extra configuration values' do 196 | context 'without custom extra config value' do 197 | cached(:chef_run) do 198 | ChefSpec::SoloRunner.new.converge(described_recipe) 199 | end 200 | 201 | it 'does not have any extra config options' do 202 | expect(chef_run).to render_file('/etc/ssh/ssh_config') 203 | expect(chef_run).not_to render_file('/etc/ssh/ssh_config'). 204 | with_content(/^# Extra Configuration Options/) 205 | end 206 | end 207 | 208 | context 'with custom extra config value' do 209 | cached(:chef_run) do 210 | ChefSpec::SoloRunner.new do |node| 211 | node.normal['ssh-hardening']['ssh']['client']['extras']['#ExtraConfig'] = 'Value' 212 | end.converge(described_recipe) 213 | end 214 | 215 | it 'uses the extra config attributes' do 216 | expect(chef_run).to render_file('/etc/ssh/ssh_config').with_content(/^# Extra Configuration Options/) 217 | expect(chef_run).to render_file('/etc/ssh/ssh_config').with_content(/^#ExtraConfig Value/) 218 | end 219 | end 220 | end 221 | 222 | describe 'version specific options' do 223 | context 'running with OpenSSH < 7.6' do 224 | it 'should have RhostsRSAAuthentication and RSAAuthentication' do 225 | expect(chef_run).to render_file('/etc/ssh/ssh_config').with_content(/RhostsRSAAuthentication/) 226 | expect(chef_run).to render_file('/etc/ssh/ssh_config').with_content(/RSAAuthentication/) 227 | end 228 | end 229 | 230 | context 'running with OpenSSH >= 7.6 on Ubuntu 18.04' do 231 | cached(:chef_run) do 232 | ChefSpec::SoloRunner.new(version: '18.04').converge(described_recipe) 233 | end 234 | 235 | it 'should not have RhostsRSAAuthentication and RSAAuthentication' do 236 | expect(chef_run).to_not render_file('/etc/ssh/ssh_config').with_content(/RhostsRSAAuthentication/) 237 | expect(chef_run).to_not render_file('/etc/ssh/ssh_config').with_content(/RSAAuthentication/) 238 | end 239 | end 240 | end 241 | 242 | context 'chef-solo' do 243 | cached(:chef_run) do 244 | ChefSpec::SoloRunner.new.converge(described_recipe) 245 | end 246 | 247 | it 'does not raise an error' do 248 | expect { chef_run }.not_to raise_error 249 | end 250 | end 251 | end 252 | -------------------------------------------------------------------------------- /libraries/devsec_ssh.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Cookbook Name:: ssh-hardening 5 | # Library:: devsec_ssh 6 | # 7 | # Copyright 2012, Dominik Richter 8 | # Copyright 2014, Christoph Hartmann 9 | # Copyright 2014, Deutsche Telekom AG 10 | # Copyright 2016, Artem Sidorenko 11 | # 12 | # Licensed under the Apache License, Version 2.0 (the "License"); 13 | # you may not use this file except in compliance with the License. 14 | # You may obtain a copy of the License at 15 | # 16 | # http://www.apache.org/licenses/LICENSE-2.0 17 | # 18 | # Unless required by applicable law or agreed to in writing, software 19 | # distributed under the License is distributed on an "AS IS" BASIS, 20 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | # See the License for the specific language governing permissions and 22 | # limitations under the License. 23 | # 24 | 25 | module DevSec 26 | # Class methods of this class can be called in order to 27 | # return different cryptographic configuration data. The calls 28 | # can be done based on the CRYPTO constant below. There is autodetection 29 | # of required ssh version. If ssh package can not be found, preconfigured OS 30 | # matrix is used. 31 | # 32 | # Syntax: 33 | # 34 | # get_[client|server]_[kexs|macs|ciphers] (enable-weak-ciphers) 35 | # 36 | # Example: 37 | # 38 | # DevSec::Ssh.get_server_macs(false) 39 | # => 'hmac-ripemd160 hmac-sha1' # for openssh server version 5.3 40 | # 41 | class Ssh # rubocop:disable Metrics/ClassLength 42 | # Fallback ssh version for autodetection 43 | FALLBACK_SSH_VERSION ||= 5.9 44 | # Support types of ssh 45 | SSH_TYPES ||= %i[client server].freeze 46 | # Crypto configuration for different ssh parameters 47 | CRYPTO ||= { 48 | kexs: { 49 | 5.3 => [], 50 | 5.9 => %w[diffie-hellman-group-exchange-sha256], 51 | 6.6 => %w[curve25519-sha256@libssh.org diffie-hellman-group-exchange-sha256], 52 | :weak => %w[diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1] 53 | }, 54 | macs: { 55 | 5.3 => %w[hmac-ripemd160 hmac-sha1], 56 | 5.9 => %w[hmac-sha2-512 hmac-sha2-256 hmac-ripemd160], 57 | 6.6 => %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com 58 | umac-128-etm@openssh.com hmac-sha2-512 hmac-sha2-256], 59 | :weak => %w[hmac-sha1] 60 | }, 61 | ciphers: { 62 | 5.3 => %w[aes256-ctr aes192-ctr aes128-ctr], 63 | 6.6 => %w[chacha20-poly1305@openssh.com aes256-gcm@openssh.com aes128-gcm@openssh.com 64 | aes256-ctr aes192-ctr aes128-ctr], 65 | :weak => %w[aes256-cbc aes192-cbc aes128-cbc] 66 | } 67 | }.freeze 68 | # Privilege separation values 69 | PRIVILEGE_SEPARATION ||= { 70 | 5.3 => 'yes', 71 | 5.9 => 'sandbox' 72 | }.freeze 73 | # Hostkey algorithms 74 | # In the current implementation they are server specific so we need own data hash for it 75 | HOSTKEY_ALGORITHMS ||= { 76 | 5.3 => %w[rsa], 77 | 6.0 => %w[rsa ecdsa], 78 | 6.6 => %w[rsa ecdsa ed25519] 79 | }.freeze 80 | 81 | class << self 82 | def get_server_privilege_separarion # rubocop:disable Style/AccessorMethodName 83 | Chef::Log.debug('Called get_server_privilege_separarion') 84 | found_ssh_version = find_ssh_version(get_ssh_server_version, PRIVILEGE_SEPARATION.keys) 85 | ret = PRIVILEGE_SEPARATION[found_ssh_version] 86 | Chef::Log.debug("Using configuration for ssh version #{found_ssh_version}, value: #{ret}") 87 | ret 88 | end 89 | 90 | def get_server_algorithms # rubocop:disable Style/AccessorMethodName 91 | Chef::Log.debug('Called get_server_algorithms') 92 | found_ssh_version = find_ssh_version(get_ssh_server_version, HOSTKEY_ALGORITHMS.keys) 93 | ret = HOSTKEY_ALGORITHMS[found_ssh_version] 94 | Chef::Log.debug("Using configuration for ssh version #{found_ssh_version}, value: #{ret}") 95 | ret 96 | end 97 | 98 | def get_client_macs(enable_weak = false) 99 | get_crypto_data(:macs, :client, enable_weak) 100 | end 101 | 102 | def get_server_macs(enable_weak = false) 103 | get_crypto_data(:macs, :server, enable_weak) 104 | end 105 | 106 | def get_client_ciphers(enable_weak = false) 107 | get_crypto_data(:ciphers, :client, enable_weak) 108 | end 109 | 110 | def get_server_ciphers(enable_weak = false) 111 | get_crypto_data(:ciphers, :server, enable_weak) 112 | end 113 | 114 | def get_client_kexs(enable_weak = false) 115 | get_crypto_data(:kexs, :client, enable_weak) 116 | end 117 | 118 | def get_server_kexs(enable_weak = false) 119 | get_crypto_data(:kexs, :server, enable_weak) 120 | end 121 | 122 | { client: 'sshclient', 123 | server: 'sshserver' }.each do |k, v| 124 | define_method("get_ssh_#{k}_version") do 125 | get_ssh_version(node['ssh-hardening'][v]['package']) 126 | end 127 | end 128 | 129 | # Verify values of permit_tunnel 130 | def validate_permit_tunnel(value) 131 | case value 132 | when true 133 | 'yes' 134 | when false 135 | 'no' 136 | when 'yes', 'no', 'point-to-point', 'ethernet' 137 | value 138 | else 139 | raise "Incorrect value for attribute node['ssh-hardening']['ssh']['server']['permit_tunnel']: must be boolean or a string as defined in the sshd_config man pages, you passed \"#{value}\"" 140 | end 141 | end 142 | 143 | private 144 | 145 | # :nocov: 146 | def node 147 | Chef.node 148 | end 149 | # :nocov: 150 | 151 | # Return the crypto data for given crypto type and ssh type 152 | # crypto_type is a subkey of CRYPTO constant, e.g. :kex, :macs, :ciphers 153 | # ssh_type is :client or :server 154 | def get_crypto_data(crypto_type, ssh_type, enable_weak) 155 | Chef::Log.debug("Called get_crypto_data for crypto_type #{crypto_type} and ssh_type #{ssh_type}") 156 | ssh_version = send("get_ssh_#{ssh_type}_version") 157 | Chef::Log.debug("Detected ssh version #{ssh_version}") 158 | found_ssh_version = find_ssh_version(ssh_version, CRYPTO[crypto_type].keys) 159 | Chef::Log.debug("Using configuration for ssh version #{found_ssh_version}") 160 | crypto = [] 161 | crypto += CRYPTO[crypto_type][found_ssh_version] 162 | # Sometimes we do not have a value, e.g. this feature is not supported 163 | # on the particilar ssh version. Return nil in such cases 164 | if crypto.empty? 165 | Chef::Log.debug("No value present for ssh version #{found_ssh_version}. Returning nil.") 166 | return nil 167 | end 168 | 169 | if enable_weak 170 | weak = CRYPTO[crypto_type][:weak] 171 | Chef::Log.info("Enabling weak #{crypto_type}: #{weak}") 172 | crypto += weak 173 | end 174 | 175 | Chef::Log.debug("get_crypto_data result for crypto_type #{crypto_type} and ssh_type #{ssh_type}: #{crypto}") 176 | 177 | crypto.uniq.join(',') 178 | end 179 | 180 | # Find the ssh version, matching to the next small 181 | # version in versions array 182 | def find_ssh_version(version, versions) 183 | found_ssh_version = nil 184 | versions.map do |v| 185 | next unless v.is_a?(Float) # skip everything what does not look like a version 186 | found_ssh_version = v if version >= v 187 | end 188 | 189 | raise "Unsupported ssh version #{version}" unless found_ssh_version 190 | 191 | found_ssh_version 192 | end 193 | 194 | def get_ssh_version(package) 195 | version = node['packages'][package]['version'] 196 | # on debian we get the epoch in front of version number: 1:7.2p2-4ubuntu2.1 197 | version = version.split(':')[1] if node['platform_family'] == 'debian' 198 | Chef::Log.debug("Detected openssh version #{version} for package #{package}") 199 | version.to_f 200 | rescue NoMethodError 201 | # Detection via installed package failed, lets guess the installed version 202 | version = guess_ssh_version 203 | Chef::Log.debug("Failed to get the openssh version from installed package. Guessed version is #{version}") 204 | version 205 | end 206 | 207 | # Guess the version of ssh via OS matrix 208 | def guess_ssh_version # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize 209 | family = node['platform_family'] 210 | platform = node['platform'] 211 | version = node['platform_version'].to_f 212 | 213 | case family 214 | when 'debian' 215 | case platform 216 | when 'ubuntu' 217 | return 6.6 if version >= 14.04 218 | when 'debian' 219 | return 6.6 if version >= 8 220 | return 6.0 if version >= 7 221 | return 5.3 if version <= 6 222 | end 223 | when 'rhel' 224 | return 6.6 if version >= 7 225 | return 5.3 if version >= 6 226 | when 'fedora' 227 | return 7.3 if version >= 25 228 | return 7.2 if version >= 24 229 | when 'suse' 230 | case platform 231 | when 'opensuse' 232 | return 6.6 if version >= 13.2 233 | when 'opensuseleap' 234 | return 7.2 if version >= 42.1 235 | end 236 | end 237 | Chef::Log.info("Unknown platform #{node['platform']} with version #{node['platform_version']} and family #{node['platform_family']}. Assuming ssh version #{FALLBACK_SSH_VERSION}") 238 | FALLBACK_SSH_VERSION 239 | end 240 | end 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /templates/default/opensshd.conf.erb: -------------------------------------------------------------------------------- 1 | <% node['ssh-hardening']['config_disclaimer'].to_s.split("\n").each do |l| %> 2 | # <%= l %> 3 | <% end %> 4 | #--- 5 | 6 | # This is the ssh client system-wide configuration file. 7 | # See sshd_config(5) for more information on any settings used. Comments will be added only to clarify why a configuration was chosen. 8 | # 9 | # Created for OpenSSH v5.9 up to 6.8 10 | 11 | # Basic configuration 12 | # =================== 13 | 14 | # Either disable or only allow root login via certificates. 15 | <% if @node['ssh-hardening']['ssh']['server']['allow_root_with_key'] %> 16 | PermitRootLogin without-password 17 | <% else %> 18 | PermitRootLogin no 19 | <% end %> 20 | 21 | # Define which port sshd should listen to. Default to `22`. 22 | <% Array(@node['ssh-hardening']['ssh']['ports']).each do |ssh_port| %> 23 | Port <%= ssh_port %> 24 | <% end %> 25 | 26 | # Address family should always be limited to the active network configuration. 27 | AddressFamily <%= ((@node['ssh-hardening']['network']['ipv6']['enable']) ? "any" : "inet" ) %> 28 | 29 | # Define which addresses sshd should listen to. Default to `0.0.0.0`, ie make sure you put your desired address in here, since otherwise sshd will listen to everyone. 30 | <% Array(@node['ssh-hardening']['ssh']['server']['listen_to']).each do |ssh_ip| %> 31 | ListenAddress <%= ssh_ip %> 32 | <% end %> 33 | 34 | # List HostKeys here. 35 | <% @hostkeys.each do |host_key_file| %> 36 | HostKey <%= host_key_file %> # Req 20 37 | <% end %> 38 | 39 | 40 | # Security configuration 41 | # ====================== 42 | 43 | # Set the protocol version to 2 for security reasons. Disables legacy support. 44 | Protocol 2 45 | 46 | # Make sure sshd checks file modes and ownership before accepting logins. This prevents accidental misconfiguration. 47 | StrictModes yes 48 | 49 | # Logging, obsoletes QuietMode and FascistLogging 50 | SyslogFacility AUTH 51 | LogLevel <%= @node['ssh-hardening']['ssh']['server']['log_level'].upcase %> 52 | 53 | # Cryptography 54 | # ------------ 55 | 56 | # **Ciphers** -- If your clients don't support CTR (eg older versions), cbc will be added 57 | # CBC: is true if you want to connect with OpenSSL-base libraries 58 | # eg ruby Net::SSH::Transport::CipherFactory requires cbc-versions of the given openssh ciphers to work 59 | # -- see: (http://net-ssh.github.com/net-ssh/classes/Net/SSH/Transport/CipherFactory.html) 60 | # 61 | <% if @cipher %> 62 | Ciphers <%= @cipher %> 63 | <% end %> 64 | 65 | # **Hash algorithms** -- Make sure not to use SHA1 for hashing, unless it is really necessary. 66 | # Weak HMAC is sometimes required if older package versions are used 67 | # eg Ruby's Net::SSH at around 2.2.* doesn't support sha2 for hmac, so this will have to be set true in this case. 68 | # 69 | <% if @mac %> 70 | MACs <%= @mac %> 71 | <% end %> 72 | 73 | # Alternative setting, if OpenSSH version is below v5.9 74 | #MACs hmac-ripemd160 75 | 76 | # **Key Exchange Algorithms** -- Make sure not to use SHA1 for kex, unless it is really necessary 77 | # Weak kex is sometimes required if older package versions are used 78 | # eg ruby's Net::SSH at around 2.2.* doesn't support sha2 for kex, so this will have to be set true in this case. 79 | # based on: https://bettercrypto.org/static/applied-crypto-hardening.pdf 80 | <% if @kex %> 81 | KexAlgorithms <%= @kex %> 82 | <% end %> 83 | 84 | # Authentication 85 | # -------------- 86 | 87 | # Secure Login directives. 88 | <% if @version.to_f < 7.4 %> 89 | UseLogin no 90 | <% end %> 91 | <% if @version.to_f < 7.5 %> 92 | UsePrivilegeSeparation <%= @use_priv_sep %> 93 | <% end %> 94 | PermitUserEnvironment no 95 | LoginGraceTime <%= @node['ssh-hardening']['ssh']['server']['login_grace_time'] %> 96 | MaxAuthTries <%= @node['ssh-hardening']['ssh']['server']['max_auth_tries'] %> 97 | MaxSessions <%= @node['ssh-hardening']['ssh']['server']['max_sessions'] %> 98 | MaxStartups <%= @node['ssh-hardening']['ssh']['server']['max_startups'] %> 99 | 100 | # Enable public key authentication 101 | PubkeyAuthentication yes 102 | 103 | <% if @node['ssh-hardening']['ssh']['server']['authorized_keys_path'] %> 104 | # Customized authorized keys path 105 | AuthorizedKeysFile <%= @node['ssh-hardening']['ssh']['server']['authorized_keys_path'] %> 106 | <% end %> 107 | 108 | # Never use host-based authentication. It can be exploited. 109 | IgnoreRhosts yes 110 | IgnoreUserKnownHosts yes 111 | HostbasedAuthentication no 112 | 113 | # Enable PAM to enforce system wide rules 114 | UsePAM <%= ((@node['ssh-hardening']['ssh']['server']['use_pam']) ? "yes" : "no" ) %> 115 | # Disable password-based authentication, it can allow for potentially easier brute-force attacks. 116 | <% passsword_auth = @node['ssh-hardening']['ssh']['server']['password_authentication'] || !!@node['ssh-hardening']['ssh']['password_authentication'] -%> 117 | PasswordAuthentication <%= (passsword_auth ? "yes" : "no" ) %> 118 | PermitEmptyPasswords no 119 | ChallengeResponseAuthentication <%= (@node['ssh-hardening']['ssh']['server']['challenge_response_authentication'] ? "yes" : "no" ) %> 120 | 121 | # Only enable Kerberos authentication if it is configured. 122 | KerberosAuthentication no 123 | KerberosOrLocalPasswd no 124 | KerberosTicketCleanup yes 125 | #KerberosGetAFSToken no 126 | 127 | # Only enable GSSAPI authentication if it is configured. 128 | GSSAPIAuthentication no 129 | GSSAPICleanupCredentials yes 130 | 131 | <% unless @node['ssh-hardening']['ssh']['server']['deny_users'].empty? %> 132 | DenyUsers <%= @node['ssh-hardening']['ssh']['server']['deny_users'].join(' ') %> 133 | <% else %> 134 | #DenyUsers * 135 | <% end %> 136 | <% unless @node['ssh-hardening']['ssh']['server']['allow_users'].empty? %> 137 | AllowUsers <%= @node['ssh-hardening']['ssh']['server']['allow_users'].join(' ') %> 138 | <% else %> 139 | #AllowUsers user1 140 | <% end %> 141 | <% unless @node['ssh-hardening']['ssh']['server']['deny_groups'].empty? %> 142 | DenyGroups <%= @node['ssh-hardening']['ssh']['server']['deny_groups'].join(' ') %> 143 | <% else %> 144 | #DenyGroups * 145 | <% end %> 146 | <% unless @node['ssh-hardening']['ssh']['server']['allow_groups'].empty? %> 147 | AllowGroups <%= @node['ssh-hardening']['ssh']['server']['allow_groups'].join(' ') %> 148 | <% else %> 149 | #AllowGroups group1 150 | <% end %> 151 | 152 | 153 | # Network 154 | # ------- 155 | 156 | # Disable TCP keep alive since it is spoofable. Use ClientAlive messages instead, they use the encrypted channel 157 | TCPKeepAlive no 158 | 159 | # Manage `ClientAlive..` signals via interval and maximum count. This will periodically check up to a `..CountMax` number of times within `..Interval` timeframe, and abort the connection once these fail. 160 | ClientAliveInterval <%= @node['ssh-hardening']['ssh']['server']['client_alive_interval'] %> 161 | ClientAliveCountMax <%= @node['ssh-hardening']['ssh']['server']['client_alive_count'] %> 162 | 163 | # Disable tunneling 164 | PermitTunnel <%= @permit_tunnel %> 165 | 166 | # Disable forwarding tcp connections. 167 | # no real advantage without denied shell access 168 | AllowTcpForwarding <%= ((@node['ssh-hardening']['ssh']['server']['allow_tcp_forwarding']) ? 'yes' : 'no' ) %> 169 | 170 | # Disable agent formwarding, since local agent could be accessed through forwarded connection. 171 | # no real advantage without denied shell access 172 | AllowAgentForwarding <%= ((@node['ssh-hardening']['ssh']['server']['allow_agent_forwarding']) ? 'yes' : 'no' ) %> 173 | 174 | # Do not allow remote port forwardings to bind to non-loopback addresses. 175 | GatewayPorts no 176 | 177 | # Disable X11 forwarding, since local X11 display could be accessed through forwarded connection. 178 | X11Forwarding <%= ((@node['ssh-hardening']['ssh']['server']['allow_x11_forwarding']) ? 'yes' : 'no' ) %> 179 | X11UseLocalhost yes 180 | 181 | 182 | # Misc. configuration 183 | # =================== 184 | 185 | 186 | PrintMotd <%= ((@node['ssh-hardening']['ssh']['server']['print_motd']) ? 'yes' : 'no' ) %> 187 | PrintLastLog <%= ((@node['ssh-hardening']['ssh']['server']['print_last_log']) ? 'yes' : 'no' ) %> 188 | Banner <%= @node['ssh-hardening']['ssh']['server']['banner'] ? @node['ssh-hardening']['ssh']['server']['banner'] : 'none' %> 189 | 190 | <% if @node['platform_family'] == 'debian' %> 191 | DebianBanner <%= @node['ssh-hardening']['ssh']['server']['os_banner'] ? 'yes' : 'no' %> 192 | <% end %> 193 | 194 | <% if @node['ssh-hardening']['ssh']['server']['use_dns'].nil? %> 195 | # Since OpenSSH 6.8, this value defaults to 'no' 196 | #UseDNS no 197 | <% else %> 198 | UseDNS <%= ((@node['ssh-hardening']['ssh']['server']['use_dns']) ? 'yes' : 'no' ) %> 199 | <% end %> 200 | #PidFile /var/run/sshd.pid 201 | #MaxStartups 10 202 | #ChrootDirectory none 203 | #ChrootDirectory /home/%u 204 | 205 | <% unless @node['ssh-hardening']['ssh']['server']['accept_env'].empty? %> 206 | # Accept locale environment variables 207 | AcceptEnv <%= @node['ssh-hardening']['ssh']['server']['accept_env'].join(' ') %> 208 | <% end %> 209 | 210 | <%- unless @node['ssh-hardening']['ssh']['server']['extras'].empty? %> 211 | # Extra Configuration Options 212 | <%- @node['ssh-hardening']['ssh']['server']['extras'].each do |key, value| %> 213 | <%= key %> <%= value %> 214 | <% end -%> 215 | <% end -%> 216 | 217 | <% if @node['ssh-hardening']['ssh']['server']['sftp']['enable'] %> 218 | # Configuration, in case SFTP is used 219 | ## override default of no subsystems 220 | ## Subsystem sftp /opt/app/openssh5/libexec/sftp-server 221 | Subsystem sftp internal-sftp -l <%= @node['ssh-hardening']['ssh']['server']['sftp']['log_level'] %> 222 | 223 | ## These lines must appear at the *end* of sshd_config 224 | Match Group <%= @node['ssh-hardening']['ssh']['server']['sftp']['group'] %> 225 | ForceCommand internal-sftp -l <%= @node['ssh-hardening']['ssh']['server']['sftp']['log_level'] %> 226 | ChrootDirectory <%= @node['ssh-hardening']['ssh']['server']['sftp']['chroot'] %> 227 | <% if @node['ssh-hardening']['ssh']['server']['sftp']['authorized_keys_path'] %> 228 | AuthorizedKeysFile <%= @node['ssh-hardening']['ssh']['server']['sftp']['authorized_keys_path'] %> 229 | <% end %> 230 | AllowTcpForwarding no 231 | AllowAgentForwarding no 232 | PasswordAuthentication <%= ((@node['ssh-hardening']['ssh']['server']['sftp']['password_authentication']) ? 'yes' : 'no' ) %> 233 | PermitRootLogin no 234 | X11Forwarding no 235 | <% else %> 236 | # Configuration, in case SFTP is used 237 | ## override default of no subsystems 238 | ## Subsystem sftp /opt/app/openssh5/libexec/sftp-server 239 | #Subsystem sftp internal-sftp -l VERBOSE 240 | # 241 | ## These lines must appear at the *end* of sshd_config 242 | #Match Group sftponly 243 | #ForceCommand internal-sftp -l VERBOSE 244 | #ChrootDirectory /sftpchroot/home/%u 245 | #AuthorizedKeysFile /sftpchroot/home/%u/.ssh/authorized_keys 246 | #AllowTcpForwarding no 247 | #AllowAgentForwarding no 248 | #PasswordAuthentication no 249 | #PermitRootLogin no 250 | #X11Forwarding no 251 | <% end %> 252 | 253 | <%- unless @node['ssh-hardening']['ssh']['server']['match_blocks'].empty? %> 254 | # Match Configuration Blocks 255 | <%- @node['ssh-hardening']['ssh']['server']['match_blocks'].each do |key, value| %> 256 | Match <%= key %> 257 | <%= value.split("\n").join("\n ") %> 258 | <% end -%> 259 | <% end -%> 260 | -------------------------------------------------------------------------------- /spec/libraries/devsec_ssh_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # 4 | # Copyright 2016, Artem Sidorenko 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'spec_helper' 20 | 21 | # Simple mock for Chef::Log 22 | class Chef 23 | class Log 24 | def self.info(*); end 25 | 26 | def self.debug(*); end 27 | end 28 | end 29 | 30 | describe DevSec::Ssh do 31 | subject { DevSec::Ssh } 32 | let(:family) { 'debian' } 33 | let(:platform) { 'ubuntu' } 34 | let(:version) { '16.04' } 35 | let(:package_installed) { true } 36 | let(:package_version) { '1:7.2p2-4ubuntu2.1' } 37 | let(:package_name) { 'openssh-server' } 38 | let(:node) do 39 | node = { 40 | 'platform_family' => family, 41 | 'platform' => platform, 42 | 'platform_version' => version, 43 | 'ssh-hardening' => { 44 | 'sshclient' => { 45 | 'package' => package_name 46 | }, 47 | 'sshserver' => { 48 | 'package' => package_name 49 | } 50 | } 51 | } 52 | node['packages'] = { package_name => { 'version' => package_version } } if package_installed 53 | 54 | node 55 | end 56 | 57 | before :each do 58 | # Stub the node object of Chef 59 | allow(subject).to receive(:node).and_return(node) 60 | end 61 | 62 | describe 'guess_ssh_version' do 63 | context 'when running on Ubuntu 16.04' do 64 | let(:family) { 'debian' } 65 | let(:platform) { 'ubuntu' } 66 | let(:version) { '16.04' } 67 | 68 | it 'should return ssh version 6.6' do 69 | expect(subject.send(:guess_ssh_version)).to eq 6.6 70 | end 71 | end 72 | 73 | context 'when running on Debian 8' do 74 | let(:family) { 'debian' } 75 | let(:platform) { 'debian' } 76 | let(:version) { '8.6' } 77 | 78 | it 'should return ssh version 6.6' do 79 | expect(subject.send(:guess_ssh_version)).to eq 6.6 80 | end 81 | end 82 | 83 | context 'when running on Debian 5' do 84 | let(:family) { 'debian' } 85 | let(:platform) { 'debian' } 86 | let(:version) { '5.0' } 87 | 88 | it 'should return ssh version 5.3' do 89 | expect(subject.send(:guess_ssh_version)).to eq 5.3 90 | end 91 | end 92 | 93 | context 'when running on RHEL 7' do 94 | let(:family) { 'rhel' } 95 | let(:platform) { 'centos' } 96 | let(:version) { '7.2.1511' } 97 | 98 | it 'should return ssh version 6.6' do 99 | expect(subject.send(:guess_ssh_version)).to eq 6.6 100 | end 101 | end 102 | 103 | context 'when running on RHEL 6' do 104 | let(:family) { 'rhel' } 105 | let(:platform) { 'centos' } 106 | let(:version) { '6.8' } 107 | 108 | it 'should return ssh version 5.3' do 109 | expect(subject.send(:guess_ssh_version)).to eq 5.3 110 | end 111 | end 112 | 113 | context 'when running on Fedora 25' do 114 | let(:family) { 'fedora' } 115 | let(:platform) { 'fedora' } 116 | let(:version) { '25' } 117 | 118 | it 'should return ssh version 7.3' do 119 | expect(subject.send(:guess_ssh_version)).to eq 7.3 120 | end 121 | end 122 | 123 | context 'when running on Fedora 24' do 124 | let(:family) { 'fedora' } 125 | let(:platform) { 'fedora' } 126 | let(:version) { '24' } 127 | 128 | it 'should return ssh version 7.2' do 129 | expect(subject.send(:guess_ssh_version)).to eq 7.2 130 | end 131 | end 132 | 133 | context 'when running on Opensuse 13.2' do 134 | let(:family) { 'suse' } 135 | let(:platform) { 'opensuse' } 136 | let(:version) { '13.2' } 137 | 138 | it 'should return ssh version 6.6' do 139 | expect(subject.send(:guess_ssh_version)).to eq 6.6 140 | end 141 | end 142 | 143 | context 'when running on Opensuse 42.2' do 144 | let(:family) { 'suse' } 145 | let(:platform) { 'opensuseleap' } 146 | let(:version) { '42.2' } 147 | 148 | it 'should return ssh version 7.2' do 149 | expect(subject.send(:guess_ssh_version)).to eq 7.2 150 | end 151 | end 152 | 153 | context 'when running on unknown platform' do 154 | let(:family) { 'unknown' } 155 | let(:platform) { 'unknown' } 156 | let(:version) { 'unknown' } 157 | 158 | it 'should return the fallback ssh version' do 159 | expect(subject.send(:guess_ssh_version)).to eq subject::FALLBACK_SSH_VERSION 160 | end 161 | end 162 | end 163 | 164 | describe 'get_ssh_version' do 165 | context 'when ssh server package is installed' do 166 | let(:package_installed) { true } 167 | let(:package_name) { 'openssh-server' } 168 | 169 | context 'with version 6.6 on rhel system' do 170 | let(:package_version) { '6.6.1p1' } 171 | let(:family) { 'rhel' } 172 | 173 | it 'should return the ssh version 6.6' do 174 | expect(subject.send(:get_ssh_version, package_name)).to eq 6.6 175 | end 176 | end 177 | 178 | context 'with version 1:7.2p2-4ubuntu2.1 on debian system' do 179 | let(:package_version) { '1:7.2p2-4ubuntu2.1' } 180 | let(:family) { 'debian' } 181 | 182 | it 'should return the ssh version 7.2' do 183 | expect(subject.send(:get_ssh_version, package_name)).to eq 7.2 184 | end 185 | end 186 | end 187 | 188 | context 'when ssh package is not installed' do 189 | let(:package_installed) { false } 190 | 191 | it 'should guess the ssh version' do 192 | expect(subject).to receive(:guess_ssh_version) 193 | subject.send(:get_ssh_version, package_name) 194 | end 195 | end 196 | end 197 | 198 | describe 'find_ssh_version' do 199 | context 'when it gets the valid ssh version' do 200 | it 'should return the next small version' do 201 | expect(subject.send(:find_ssh_version, 5.7, [5.3, 5.9, 6.6])).to eq 5.3 202 | end 203 | end 204 | context 'when it gets the invalid ssh version' do 205 | it 'should raise an exception' do 206 | expect { subject.send(:find_ssh_version, 3.0, [5.3, 5.9, 6.6]) }.to raise_exception(/Unsupported ssh version/) 207 | end 208 | end 209 | end 210 | 211 | describe 'get_server_privilege_separation' do 212 | DevSec::Ssh::PRIVILEGE_SEPARATION.each do |openssh_version, conf_data| 213 | context "when openssh is >= #{openssh_version}" do 214 | before :each do 215 | # mock get_ssh_server_version. We test it somewhere else 216 | expect(subject).to receive(:get_ssh_server_version) { openssh_version } 217 | end 218 | 219 | it "get the config value #{conf_data}" do 220 | expect(subject.get_server_privilege_separarion).to eq conf_data 221 | end 222 | end 223 | end 224 | context 'when openssh has a totally unsupported version, e.g. 3.0' do 225 | it 'should raise an exception' do 226 | expect(subject).to receive(:get_ssh_server_version) { 3.0 } 227 | expect { subject.get_server_privilege_separarion }.to raise_exception(/Unsupported ssh version/) 228 | end 229 | end 230 | end 231 | 232 | describe 'get_server_algorithms' do 233 | DevSec::Ssh::HOSTKEY_ALGORITHMS.each do |openssh_version, conf_data| 234 | context "when openssh is >= #{openssh_version}" do 235 | before :each do 236 | # mock get_ssh_server_version. We test it somewhere else 237 | expect(subject).to receive(:get_ssh_server_version) { openssh_version } 238 | end 239 | 240 | it "get the config value #{conf_data}" do 241 | expect(subject.get_server_algorithms).to eq conf_data 242 | end 243 | end 244 | end 245 | context 'when openssh has a totally unsupported version, e.g. 3.0' do 246 | it 'should raise an exception' do 247 | expect(subject).to receive(:get_ssh_server_version) { 3.0 } 248 | expect { subject.get_server_algorithms }.to raise_exception(/Unsupported ssh version/) 249 | end 250 | end 251 | end 252 | 253 | # Here we test the public functions: 254 | # get_[client|server]_[kexs|macs|ciphers] 255 | # In order to cover all possible combinations, we need a complex nested loops:-\ 256 | # We start with client|server combination 257 | %w[client server].each do |type| 258 | # Go over different types of crypto parameters, e.g. kexs, macs, ciphers 259 | DevSec::Ssh::CRYPTO.each do |crypto_type, crypto_value| # we can not use subject here, as its not in the block 260 | function = "get_#{type}_#{crypto_type}" 261 | 262 | # here we start to describe a specific function like get_client_kexs 263 | describe function do 264 | # Go over different ssh versions 265 | crypto_value.each do |openssh_version, crypto_params| 266 | next unless openssh_version.is_a?(Float) # skip :weak 267 | 268 | context "when openssh is >= #{openssh_version}" do 269 | before :each do 270 | # mock get_ssh_[client|server]_version. We test it somewhere else 271 | expect(subject).to receive("get_ssh_#{type}_version") { openssh_version } 272 | end 273 | 274 | if crypto_params.empty? 275 | it 'get nil' do 276 | expect(subject.send(function)).to eq nil 277 | end 278 | else 279 | # Check the different combinations of weak_parameter to the function 280 | [true, false].each do |weak_option| 281 | context "and when weak option is #{weak_option}" do 282 | let(:ret) { subject.send(function, weak_option).split(',') } 283 | 284 | it "get crypto parameters #{weak_option ? 'with' : 'without'} weak options" do 285 | exp = weak_option ? (crypto_params | crypto_value[:weak]) : crypto_params 286 | expect(ret).to eq exp 287 | end 288 | end 289 | end 290 | end 291 | end 292 | end 293 | 294 | context 'when openssh has a totally unsupported version, e.g. 3.0' do 295 | it 'should raise an exception' do 296 | expect(subject).to receive("get_ssh_#{type}_version".to_sym) { 3.0 } 297 | expect { subject.send(function) }.to raise_exception(/Unsupported ssh version/) 298 | end 299 | end 300 | end 301 | end 302 | end 303 | 304 | describe 'get_ssh_server_version' do 305 | it 'should call get_ssh_version with server package attribute' do 306 | expect(subject).to receive(:get_ssh_version).with(package_name) 307 | subject.send(:get_ssh_server_version) 308 | end 309 | end 310 | 311 | describe 'get_ssh_client_version' do 312 | it 'should call get_ssh_version with client package attribute' do 313 | expect(subject).to receive(:get_ssh_version).with(package_name) 314 | subject.send(:get_ssh_client_version) 315 | end 316 | end 317 | 318 | describe 'validate_permit_tunnel' do 319 | context 'with value of false' do 320 | it 'should return no' do 321 | expect(subject.send(:validate_permit_tunnel, false)).to eq 'no' 322 | end 323 | end 324 | 325 | context 'with value of true' do 326 | it 'should return yes' do 327 | expect(subject.send(:validate_permit_tunnel, true)).to eq 'yes' 328 | end 329 | end 330 | 331 | context 'with a valid string ethernet' do 332 | it 'should return ethernet' do 333 | expect(subject.send(:validate_permit_tunnel, 'ethernet')).to eq 'ethernet' 334 | end 335 | end 336 | 337 | context 'with an invalid string' do 338 | it 'should raise exception' do 339 | expect { subject.send(:validate_permit_tunnel, 'IAmNotValid') }.to raise_exception('Incorrect value for attribute node[\'ssh-hardening\'][\'ssh\'][\'server\'][\'permit_tunnel\']: must be boolean or a string as defined in the sshd_config man pages, you passed "IAmNotValid"') 340 | end 341 | end 342 | end 343 | end 344 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh-hardening (Chef cookbook) 2 | 3 | [![Supermarket](http://img.shields.io/cookbook/v/ssh-hardening.svg)][1] 4 | [![Build Status](https://travis-ci.org/dev-sec/chef-ssh-hardening.svg?branch=master)][2] 5 | [![Code Coverage](http://img.shields.io/coveralls/dev-sec/chef-ssh-hardening.svg)][3] 6 | [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)][4] 7 | 8 | ## Description 9 | 10 | This cookbook provides secure ssh-client and ssh-server configurations. This cookbook does not provide capabilities for management of users and/or ssh keys, please use other cookbooks for that. 11 | 12 | ## Requirements 13 | 14 | * Chef >= 14.13.11 15 | 16 | ### Platform 17 | 18 | - Debian 8, 9, 10 19 | - Ubuntu 16.04, 18.04 20 | - RHEL 6, 7 21 | - CentOS 6, 7 22 | - Oracle Linux 6, 7 23 | - Fedora 29, 30 24 | - OpenSuse Leap 42 25 | - Amazon Linux 1, 2 26 | 27 | ## Attributes 28 | 29 | Below you can find the attribute documentation and their default values. 30 | 31 | Notice: Some of attribute defaults of this cookbook are set in the recipes. You should use a higher [attribute precedence](https://docs.chef.io/attributes.html#attribute-precedence) level for overriding of such attributes. Such attributes are flagged with `#override attribute#` in the list below. Example for overriding a such attribute: 32 | 33 | ```ruby 34 | override['ssh-hardening']['ssh']['server']['listen_to'] = node['ipaddress'] 35 | ``` 36 | 37 | * `['ssh-hardening']['network']['ipv6']['enable']` - `false`. Set to true if IPv6 is needed 38 | * `['ssh-hardening']['ssh']['ports']` - `22`. Ports to which ssh-server should listen to and ssh-client should connect to 39 | * `['ssh-hardening']['ssh'][{'client', 'server'}]['kex']` - `nil` to calculate best key-exchange (KEX) based on server version, otherwise specify a string of Kex values 40 | * `['ssh-hardening']['ssh'][{'client', 'server'}]['mac']` - `nil` to calculate best Message Authentication Codes (MACs) based on server version, otherwise specify a string of Mac values 41 | * `['ssh-hardening']['ssh'][{'client', 'server'}]['cipher']` - `nil` to calculate best ciphers based on server version, otherwise specify a string of Cipher values 42 | * `['ssh-hardening']['ssh'][{'client', 'server'}]['cbc_required']` - `false`. Set to `true` if CBC for ciphers is required. This is usually only necessary, if older M2M mechanism need to communicate with SSH, that don't have any of the configured secure ciphers enabled. CBC is a weak alternative. Anything weaker should be avoided and is thus not available. 43 | * `['ssh-hardening']['ssh'][{'client', 'server'}]['weak_hmac']` - `false`. Set to `true` if weaker HMAC mechanisms are required. This is usually only necessary, if older M2M mechanism need to communicate with SSH, that don't have any of the configured secure HMACs enabled. 44 | * `['ssh-hardening']['ssh'][{'client', 'server'}]['weak_kex']` - `false`. Set to `true` if weaker Key-Exchange (KEX) mechanisms are required. This is usually only necessary, if older M2M mechanism need to communicate with SSH, that don't have any of the configured secure KEXs enabled. 45 | * `['ssh-hardening']['ssh']['client']['remote_hosts']` - `[]` - one or more hosts, to which ssh-client can connect to. 46 | * `['ssh-hardening']['ssh']['client']['password_authentication']` - `false`. Set to `true` if password authentication should be enabled. 47 | * `['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. 48 | * `['ssh-hardening']['ssh']['client']['extras']` - `{}`. Add extra configuration options, see [below](#extra-configuration-options) for details 49 | * `['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`) 50 | * `['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) 51 | * `['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). 52 | * `['ssh-hardening']['ssh']['server']['dh_build_primes_size']` - `4096` - Prime length which should be generated. This option is only valid if `dh_build_primes` is enabled. 53 | * `['ssh-hardening']['ssh']['server']['listen_to']` `#override attribute#` - one or more ip addresses, to which ssh-server should listen to. Default is to listen on all interfaces. It should be configured for security reasons! 54 | * `['ssh-hardening']['ssh']['server']['allow_root_with_key']` - `false` to disable root login altogether. Set to `true` to allow root to login via key-based mechanism 55 | * `['ssh-hardening']['ssh']['server']['allow_tcp_forwarding']` - `false`. Set to `true` to allow TCP Forwarding 56 | * `['ssh-hardening']['ssh']['server']['allow_agent_forwarding']` - `false`. Set to `true` to allow Agent Forwarding 57 | * `['ssh-hardening']['ssh']['server']['allow_x11_forwarding']` - `false`. Set to `true` to allow X11 Forwarding 58 | * `['ssh-hardening']['ssh']['server']['permit_tunnel']` - `false` to disable tun device forwarding. Set to `true` to allow tun device forwarding. Other accepted values: 'yes', 'no', 'point-to-point', 'ethernet'. See `man sshd_config` for exact behaviors. Note: you'll also need to enable `allow_tcp_forwarding`. 59 | * `['ssh-hardening']['ssh']['server']['use_pam']` - `true`. Set to `false` to disable the pam authentication of sshd 60 | * `['ssh-hardening']['ssh']['server']['challenge_response_authentication']` - `false`. Set to `true` to enable challenge response authentication. 61 | * `['ssh-hardening']['ssh']['server']['deny_users']` - `[]` to configure `DenyUsers`, if specified login is disallowed for user names that match one of the patterns. 62 | * `['ssh-hardening']['ssh']['server']['allow_users']` - `[]` to configure `AllowUsers`, if specified, login is allowed only for user names that match one of the patterns. 63 | * `['ssh-hardening']['ssh']['server']['deny_groups']` - `[]` to configure `DenyGroups`, if specified, login is disallowed for users whose primary group or supplementary group list matches one of the patterns. 64 | * `['ssh-hardening']['ssh']['server']['allow_groups']` - `[]` to configure `AllowGroups`, if specified, login is allowed only for users whose primary group or supplementary group list matches one of the patterns. 65 | * `['ssh-hardening']['ssh']['server']['print_motd']` - `false`. Set to `true` to enable printing of the MOTD 66 | * `['ssh-hardening']['ssh']['server']['print_last_log']` - `false`. Set to `true` to enable printing of last login information 67 | * `['ssh-hardening']['ssh']['server']['banner']` - `nil`. Set a path like '/etc/issue.net' to enable the banner 68 | * `['ssh-hardening']['ssh']['server']['os_banner']` - `false` to disable version information during the protocol handshake (debian family only). Set to `true` to enable it 69 | * `['ssh-hardening']['ssh']['server']['use_dns']` - `nil` to use the openssh default. Set to `true` or `false` to enable/disable the DNS lookup and check of remote host 70 | * `['ssh-hardening']['ssh']['server']['use_privilege_separation']` - `nil` to calculate the best value based on server version, otherwise set `true` or `false` 71 | * `['ssh-hardening']['ssh']['server']['login_grace_time']` - `30s`. Time in which the login should be successfully, otherwise the user is disconnected. 72 | * `['ssh-hardening']['ssh']['server']['max_auth_tries']` - `2`. The number of authentication attempts per connection 73 | * `['ssh-hardening']['ssh']['server']['max_sessions']` - `10` The number of sessions per connection 74 | * `['ssh-hardening']['ssh']['server']['password_authentication']` - `false`. Set to `true` if password authentication should be enabled 75 | * `['ssh-hardening']['ssh']['server']['log_level']` - `verbose`. The log level of sshd. See `LogLevel` in `man 5 sshd_config` for possible values. 76 | * `['ssh-hardening']['ssh']['server']['sftp']['enable']` - `false`. Set to `true` to enable the SFTP feature of OpenSSH daemon 77 | * `['ssh-hardening']['ssh']['server']['sftp']['group']` - `sftponly`. Sets the `Match Group` option of SFTP to allow SFTP only for dedicated users 78 | * `['ssh-hardening']['ssh']['server']['sftp']['chroot']` - `/home/%u`. Sets the directory where the SFTP user should be chrooted 79 | * `['ssh-hardening']['ssh']['server']['sftp']['authorized_keys_path']` - `nil`. If not nil, full path to one or multiple space-separated authorized keys file that will be set inside the `Match Group` for SFTP-only access 80 | * `['ssh-hardening']['ssh']['server']['sftp']['password_authentication']` - `false`. Set to `true` if password authentication should be enabled 81 | * `['ssh-hardening']['ssh']['server']['authorized_keys_path']` - `nil`. If not nil, full path to one or multiple space-separated authorized keys file is expected. 82 | * `['ssh-hardening']['ssh']['server']['extras']` - `{}`. Add extra configuration options, see [below](#extra-configuration-options) for details 83 | * `['ssh-hardening']['ssh']['server']['match_blocks']` - `{}`. Match configuration block, see [below](#match-configuration-options-for-sshd) for details 84 | 85 | ## Usage 86 | 87 | Add the recipes to the run_list: 88 | 89 | "recipe[ssh-hardening]" 90 | 91 | This will install ssh-server and ssh-client. You can alternatively choose only one via: 92 | 93 | "recipe[ssh-hardening::server]" 94 | "recipe[ssh-hardening::client]" 95 | 96 | Configure attributes: 97 | 98 | "ssh-hardening": { 99 | "ssh" : { 100 | "server" : { 101 | "listen_to" : "10.2.3.4" 102 | } 103 | } 104 | } 105 | 106 | **The default value for `listen_to` is `0.0.0.0`. It is highly recommended to change the value.** 107 | 108 | ## SFTP 109 | 110 | To enable the SFTP configuration add one of the following recipes to the run_list: 111 | 112 | "recipe[ssh-hardening]" 113 | or 114 | "recipe[ssh-hardening::server]" 115 | 116 | Configure attributes: 117 | 118 | "ssh-hardening": { 119 | "ssh" : { 120 | "server": { 121 | "sftp" : { 122 | "enable" : true, 123 | "chroot" : "/home/sftp/%u", 124 | "group" : "sftusers" 125 | } 126 | } 127 | } 128 | } 129 | 130 | This will enable the SFTP Server and chroot every user in the `sftpusers` group to the `/home/sftp/%u` directory. 131 | 132 | ## Extra Configuration Options 133 | 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. 134 | 135 | The syntax is as follows: 136 | ``` 137 | # => Extra Server Configuration 138 | default['ssh-hardening']['ssh']['server']['extras'].tap do |extra| 139 | extra['#Some Comment'] = 'Heres the Comment' 140 | extra['AuthenticationMethods'] = 'publickey,keyboard-interactive' 141 | end 142 | 143 | # => Extra Client Configuration 144 | default['ssh-hardening']['ssh']['client']['extras'].tap do |extra| 145 | extra['PermitLocalCommand'] = 'no' 146 | extra['Tunnel'] = 'no' 147 | end 148 | ``` 149 | 150 | ## Match Configuration Options for sshd 151 | Match blocks have to be placed by the end of sshd_config. This can be achieved by using the `match_blocks` attribute tree: 152 | 153 | ``` 154 | default['ssh-hardening']['ssh']['server']['match_blocks'].tap do |match| 155 | match['User root'] = <<~ROOT 156 | AuthorizedKeysFile .ssh/authorized_keys 157 | ROOT 158 | match['User git'] = <<~GIT 159 | Banner none 160 | AuthorizedKeysCommand /bin/false 161 | AuthorizedKeysFile .ssh/authorized_keys 162 | GSSAPIAuthentication no 163 | PasswordAuthentication no 164 | GIT 165 | end 166 | ``` 167 | 168 | ## Local Testing 169 | 170 | Please install [chef-dk](https://downloads.chef.io/chefdk), [VirtualBox](https://www.virtualbox.org/) or VMware Workstation and [Vagrant](https://www.vagrantup.com/). 171 | 172 | Linting is checked with [rubocop](https://github.com/bbatsov/rubocop) and [foodcritic](http://www.foodcritic.io/): 173 | 174 | ```bash 175 | $ chef exec rake lint 176 | ..... 177 | ``` 178 | 179 | Unit/spec tests are done with [chefspec](https://github.com/sethvargo/chefspec): 180 | 181 | ```bash 182 | $ chef exec rake spec 183 | ..... 184 | ``` 185 | 186 | Integration tests are done with [test-kitchen](http://kitchen.ci/) and [inspec](https://www.inspec.io/): 187 | 188 | ```bash 189 | $ chef exec rake kitchen 190 | ..... 191 | # or you can use the kitchen directly 192 | $ kitchen test 193 | ``` 194 | 195 | ## FAQ / Pitfalls 196 | 197 | **I can't log into my account. I have registered the client key, but it still doesn't let me it.** 198 | 199 | If you have exhausted all typical issues (firewall, network, key missing, wrong key, account disabled etc.), it may be that your account is locked. The quickest way to find out is to look at the password hash for your user: 200 | 201 | sudo grep myuser /etc/shadow 202 | 203 | If the hash includes an `!`, your account is locked: 204 | 205 | myuser:!:16280:7:60:7::: 206 | 207 | The proper way to solve this is to unlock the account (`passwd -u myuser`). If the user doesn't have a password, you should can unlock it via: 208 | 209 | usermod -p "*" myuser 210 | 211 | Alternatively, if you intend to use PAM, you enabled it via `['ssh-hardening']['ssh']['use_pam'] = true`. PAM will allow locked users to get in with keys. 212 | 213 | 214 | **Why doesn't my application connect via SSH anymore?** 215 | 216 | Always look into log files first and if possible look at the negotiation between client and server that is completed when connecting. 217 | 218 | We have seen some issues in applications (based on python and ruby) that are due to their use of an outdated crypto set. This collides with this hardening module, which reduced the list of ciphers, message authentication codes (MACs) and key exchange (KEX) algorithms to a more secure selection. 219 | 220 | If you find this isn't enough, feel free to activate the attributes `cbc_requires` for ciphers, `weak_hmac` for MACs and `weak_kex`for KEX in the namespaces `['ssh-hardening']['ssh']['client']` or `['ssh-hardening']['ssh']['server']` based on where you want to support them. 221 | 222 | **Why can't I log to the SFTP server after I added a user to my SFTP group?** 223 | 224 | This is a ChrootDirectory ownership problem. sshd will reject SFTP connections to accounts that are set to chroot into any directory that has ownership/permissions that sshd considers insecure. sshd's strict ownership/permissions requirements dictate that every directory in the chroot path must be owned by root and only writable by the owner. So, for example, if the chroot environment is /home must be owned by root. 225 | 226 | See [https://wiki.archlinux.org/index.php/SFTP_chroot](https://wiki.archlinux.org/index.php/SFTP_chroot) 227 | 228 | ## Contributors + Kudos 229 | 230 | * Dominik Richter [arlimus](https://github.com/arlimus) 231 | * Christoph Hartmann [chris-rock](https://github.com/chris-rock) 232 | * Bernhard Weisshuhn (a.k.a. bernhorst) [bkw](https://github.com/bkw) 233 | * Patrick Munch [atomic111](https://github.com/atomic111) 234 | * Edmund Haselwanter [ehaselwanter](https://github.com/ehaselwanter) 235 | * Dana Merrick [dmerrick](https://github.com/dmerrick) 236 | * Anton Rieder [aried3r](https://github.com/aried3r) 237 | * Trent Petersen [Rockstar04](https://github.com/Rockstar04) 238 | * Petri Sirkkala [sirkkalap](https://github.com/sirkkalap) 239 | * Jan Klare [jklare](https://github.com/jklare) 240 | * Zac Hallett [zhallett](https://github.com/zhallett) 241 | * Petri Sirkkala [sirkkalap](https://github.com/sirkkalap) 242 | * [stribika](https://github.com/stribika) 243 | * Siddhant Rath [sidxz](https://github.com/sidxz) 244 | 245 | This cookbook is mostly based on guides by: 246 | 247 | * [NSA: Guide to the Secure Configuration of Red Hat Enterprise Linux 5](https://www.iad.gov/iad/library/ia-guidance/security-configuration/operating-systems/guide-to-the-secure-configuration-of-red-hat-enterprise.cfm) 248 | * [Deutsche Telekom, Group IT Security, Security Requirements (German)](https://www.telekom.com/psa) 249 | 250 | Thanks to all of you!! 251 | 252 | ## Contributing 253 | 254 | See [contributor guideline](CONTRIBUTING.md). 255 | 256 | ## License and Author 257 | 258 | * Author:: Dominik Richter 259 | * Author:: Deutsche Telekom AG 260 | 261 | Licensed under the Apache License, Version 2.0 (the "License"); 262 | you may not use this file except in compliance with the License. 263 | You may obtain a copy of the License at 264 | 265 | http://www.apache.org/licenses/LICENSE-2.0 266 | 267 | Unless required by applicable law or agreed to in writing, software 268 | distributed under the License is distributed on an "AS IS" BASIS, 269 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 270 | See the License for the specific language governing permissions and 271 | limitations under the License. 272 | 273 | [1]: https://supermarket.getchef.com/cookbooks/ssh-hardening 274 | [2]: http://travis-ci.org/dev-sec/chef-ssh-hardening 275 | [3]: https://coveralls.io/r/dev-sec/chef-ssh-hardening 276 | [4]: https://gitter.im/dev-sec/general 277 | -------------------------------------------------------------------------------- /spec/recipes/server_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 4 | # Copyright 2014, Deutsche Telekom AG 5 | # Copyright 2016, Artem Sidorenko 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe 'ssh-hardening::server' do 23 | let(:helper_lib) { DevSec::Ssh } 24 | let(:ssh_config_file) { '/etc/ssh/sshd_config' } 25 | let(:dh_primes_ok) { true } 26 | 27 | # converge 28 | cached(:chef_run) do 29 | ChefSpec::SoloRunner.new.converge(described_recipe) 30 | end 31 | 32 | before do 33 | stub_command("test $(awk '$5 < 2047 && $5 ~ /^[0-9]+$/ { print $5 }' /etc/ssh/moduli | uniq | wc -c) -eq 0").and_return(dh_primes_ok) 34 | end 35 | 36 | it 'should create cache directory' do 37 | expect(chef_run).to create_directory('/tmp/ssh-hardening-file-cache/ssh-hardening') 38 | end 39 | 40 | it 'installs openssh-server' do 41 | expect(chef_run).to install_package('openssh-server') 42 | end 43 | 44 | it 'creates /etc/ssh/sshd_config' do 45 | expect(chef_run).to create_template('/etc/ssh/sshd_config').with( 46 | mode: '0600', 47 | owner: 'root', 48 | group: 'root' 49 | ) 50 | end 51 | 52 | it 'enables the ssh server' do 53 | expect(chef_run).to enable_service('sshd') 54 | end 55 | 56 | it 'starts the server' do 57 | expect(chef_run).to start_service('sshd') 58 | end 59 | 60 | it 'creates the directory /etc/ssh' do 61 | expect(chef_run).to create_directory('/etc/ssh').with( 62 | mode: '0755', 63 | owner: 'root', 64 | group: 'root' 65 | ) 66 | end 67 | 68 | it 'accepts default locale environment variables' do 69 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 70 | with_content('AcceptEnv LANG LC_* LANGUAGE') 71 | end 72 | 73 | include_examples 'does not allow weak hmacs' 74 | include_examples 'does not allow weak kexs' 75 | include_examples 'does not allow weak ciphers' 76 | include_examples 'allow ctr ciphers' 77 | 78 | context 'with weak hmacs enabled for the server' do 79 | cached(:chef_run) do 80 | ChefSpec::SoloRunner.new do |node| 81 | node.normal['ssh-hardening']['ssh']['server']['weak_hmac'] = true 82 | end.converge(described_recipe) 83 | end 84 | 85 | include_examples 'allow weak hmacs' 86 | end 87 | 88 | context 'with weak hmacs enabled for only the client' do 89 | cached(:chef_run) do 90 | ChefSpec::SoloRunner.new do |node| 91 | node.normal['ssh-hardening']['ssh']['server']['client']['weak_hmac'] = true 92 | end.converge(described_recipe) 93 | end 94 | 95 | include_examples 'does not allow weak hmacs' 96 | end 97 | 98 | context 'weak_kex enabled for only the server' do 99 | cached(:chef_run) do 100 | ChefSpec::SoloRunner.new do |node| 101 | node.normal['ssh-hardening']['ssh']['server']['weak_kex'] = true 102 | end.converge(described_recipe) 103 | end 104 | 105 | include_examples 'allow weak kexs' 106 | end 107 | 108 | context 'weak_kex enabled for only the client' do 109 | cached(:chef_run) do 110 | ChefSpec::SoloRunner.new do |node| 111 | node.normal['ssh-hardening']['ssh']['client']['weak_kex'] = true 112 | end.converge(described_recipe) 113 | end 114 | 115 | include_examples 'does not allow weak kexs' 116 | end 117 | 118 | context 'cbc_required for the server only' do 119 | cached(:chef_run) do 120 | ChefSpec::SoloRunner.new do |node| 121 | node.normal['ssh-hardening']['ssh']['server']['cbc_required'] = true 122 | end.converge(described_recipe) 123 | end 124 | 125 | include_examples 'allow weak ciphers' 126 | end 127 | 128 | context 'cbc_required for the client only' do 129 | cached(:chef_run) do 130 | ChefSpec::SoloRunner.new do |node| 131 | node.normal['ssh-hardening']['ssh']['client']['cbc_required'] = true 132 | end.converge(described_recipe) 133 | end 134 | 135 | include_examples 'does not allow weak ciphers' 136 | end 137 | 138 | context 'with custom KEXs' do 139 | cached(:chef_run) do 140 | ChefSpec::SoloRunner.new do |node| 141 | node.normal['ssh-hardening']['ssh']['server']['kex'] = 'mycustomkexvalue' 142 | end.converge(described_recipe) 143 | end 144 | 145 | it 'uses the value of kex attribute' do 146 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 147 | with_content(/KexAlgorithms mycustomkexvalue/) 148 | end 149 | end 150 | 151 | context 'with custom MACs' do 152 | cached(:chef_run) do 153 | ChefSpec::SoloRunner.new do |node| 154 | node.normal['ssh-hardening']['ssh']['server']['mac'] = 'mycustommacvalue' 155 | end.converge(described_recipe) 156 | end 157 | 158 | it 'uses the value of mac attribute' do 159 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 160 | with_content(/MACs mycustommacvalue/) 161 | end 162 | end 163 | 164 | context 'with custom ciphers' do 165 | cached(:chef_run) do 166 | ChefSpec::SoloRunner.new do |node| 167 | node.normal['ssh-hardening']['ssh']['server']['cipher'] = 'mycustomciphervalue' 168 | end.converge(described_recipe) 169 | end 170 | 171 | it 'uses the value of cipher attribute' do 172 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 173 | with_content(/Ciphers mycustomciphervalue/) 174 | end 175 | end 176 | 177 | it 'restarts the ssh server on config changes' do 178 | resource = chef_run.template('/etc/ssh/sshd_config') 179 | expect(resource).to notify('service[sshd]').to(:restart).delayed 180 | end 181 | 182 | context 'without attribute allow_root_with_key' do 183 | it 'does not unlock root account' do 184 | expect(chef_run).to_not run_execute('unlock root account if it is locked') 185 | end 186 | end 187 | 188 | context 'with attribute allow_root_with_key' do 189 | cached(:chef_run) do 190 | ChefSpec::SoloRunner.new do |node| 191 | node.normal['ssh-hardening']['ssh']['allow_root_with_key'] = true 192 | end.converge(described_recipe) 193 | end 194 | 195 | it 'unlocks root account' do 196 | expect(chef_run).to run_execute('unlock root account if it is locked'). 197 | with(command: "sed 's/^root:\!/root:*/' /etc/shadow -i") 198 | end 199 | end 200 | 201 | context 'chef-solo' do 202 | cached(:chef_run) do 203 | ChefSpec::SoloRunner.new.converge(described_recipe) 204 | end 205 | 206 | it 'does not raise an error' do 207 | expect { chef_run }.not_to raise_error 208 | end 209 | end 210 | 211 | it 'disables the login banner' do 212 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 213 | with_content(/Banner none/) 214 | end 215 | 216 | context 'with provided login banner path' do 217 | cached(:chef_run) do 218 | ChefSpec::SoloRunner.new do |node| 219 | node.normal['ssh-hardening']['ssh']['server']['banner'] = '/etc/ssh/banner' 220 | end.converge(described_recipe) 221 | end 222 | 223 | it 'uses the given login banner' do 224 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 225 | with_content(/Banner \/etc\/ssh\/banner/) 226 | end 227 | end 228 | 229 | describe 'permit_tunnel options' do 230 | let(:permit_tunnel) { false } 231 | 232 | let(:chef_run) do 233 | ChefSpec::SoloRunner.new do |node| 234 | node.normal['ssh-hardening']['ssh']['server']['permit_tunnel'] = permit_tunnel 235 | end.converge(described_recipe) 236 | end 237 | 238 | context 'with default value of false' do 239 | it 'should set PermitTunnel to no' do 240 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('PermitTunnel no') 241 | end 242 | end 243 | 244 | context 'with value of true' do 245 | let(:permit_tunnel) { true } 246 | it 'should set PermitTunnel to yes' do 247 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('PermitTunnel yes') 248 | end 249 | end 250 | 251 | context 'with a valid string' do 252 | let(:permit_tunnel) { 'ethernet' } 253 | it 'should set PermitTunnel to ethernet' do 254 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('PermitTunnel ethernet') 255 | end 256 | end 257 | end 258 | 259 | it 'should set UsePAM to yes per default' do 260 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UsePAM yes') 261 | end 262 | 263 | describe 'version specific options' do 264 | context 'running with OpenSSH < 7.4' do 265 | it 'should have UseLogin' do 266 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UseLogin') 267 | end 268 | 269 | it 'should have UsePrivilegeSeparation' do 270 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UsePrivilegeSeparation') 271 | end 272 | end 273 | 274 | context 'running with OpenSSH >= 7.4 on RHEL 7' do 275 | let(:chef_run) do 276 | ChefSpec::SoloRunner.new(platform: 'centos', version: '7.5.1804').converge(described_recipe) 277 | end 278 | 279 | before do 280 | stub_command('getenforce | grep -vq Disabled && semodule -l | grep -q ssh_password').and_return(true) 281 | end 282 | 283 | it 'should not have UseLogin' do 284 | expect(chef_run).to_not render_file('/etc/ssh/sshd_config').with_content('UseLogin') 285 | end 286 | end 287 | 288 | context 'running with Openssh >= 7.5 on Ubuntu 18.04' do 289 | let(:chef_run) do 290 | ChefSpec::SoloRunner.new(version: '18.04').converge(described_recipe) 291 | end 292 | 293 | it 'should not have UseLogin' do 294 | expect(chef_run).to_not render_file('/etc/ssh/sshd_config').with_content('UseLogin') 295 | end 296 | 297 | it 'should not have UsePrivilegeSeparation' do 298 | expect(chef_run).to_not render_file('/etc/ssh/sshd_config').with_content('UsePrivilegeSeparation') 299 | end 300 | end 301 | end 302 | 303 | describe 'UsePAM option' do 304 | let(:use_pam) { true } 305 | 306 | let(:chef_run) do 307 | ChefSpec::SoloRunner.new(platform: platform, version: version) do |node| 308 | node.normal['ssh-hardening']['ssh']['server']['use_pam'] = use_pam 309 | end.converge(described_recipe) 310 | end 311 | 312 | context 'when running on Ubuntu' do 313 | let(:platform) { 'ubuntu' } 314 | let(:version) { '16.04' } 315 | 316 | it 'does not invoke any SELinux resources' do 317 | expect(chef_run).not_to render_file('/tmp/ssh-hardening-file-cache/ssh-hardening/ssh_password.te') 318 | expect(chef_run).not_to run_execute('remove selinux policy') 319 | expect(chef_run).not_to run_bash('build selinux package and install it') 320 | expect(chef_run).not_to install_package('policycoreutils-python') 321 | end 322 | 323 | context 'when use_pam is set to true' do 324 | let(:use_pam) { true } 325 | 326 | it 'should set UsePAM to yes' do 327 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UsePAM yes') 328 | end 329 | end 330 | 331 | context 'when use_pam is set to false' do 332 | let(:use_pam) { false } 333 | 334 | it 'should set UsePAM to no' do 335 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UsePAM no') 336 | end 337 | end 338 | end 339 | 340 | context 'when running on CentOS' do 341 | let(:platform) { 'centos' } 342 | let(:version) { '7.5.1804' } 343 | 344 | let(:selinux_disabled_or_policy_removed) { false } 345 | let(:selinux_enabled_and_policy_installed) { false } 346 | 347 | before do 348 | stub_command('getenforce | grep -vq Disabled && semodule -l | grep -q ssh_password').and_return(selinux_enabled_and_policy_installed) 349 | stub_command('getenforce | grep -q Disabled || semodule -l | grep -q ssh_password').and_return(selinux_disabled_or_policy_removed) 350 | end 351 | 352 | it 'should install selinux tools' do 353 | expect(chef_run).to install_package('policycoreutils-python') 354 | end 355 | 356 | context 'when use_pam is set to true' do 357 | let(:use_pam) { true } 358 | 359 | it 'should set UsePAM to yes' do 360 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UsePAM yes') 361 | end 362 | 363 | context 'when selinux is disabled or policy is removed' do 364 | let(:selinux_enabled_and_policy_installed) { false } 365 | 366 | it 'should not invoke the policy removal' do 367 | expect(chef_run).not_to run_execute('remove selinux policy') 368 | end 369 | end 370 | 371 | context 'when selinux is enabled and policy is present' do 372 | let(:selinux_enabled_and_policy_installed) { true } 373 | 374 | it 'should invoke the policy removal' do 375 | expect(chef_run).to run_execute('remove selinux policy') 376 | end 377 | end 378 | end 379 | 380 | context 'when use_pam is set to false' do 381 | let(:use_pam) { false } 382 | 383 | it 'should set UsePAM to no' do 384 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content('UsePAM no') 385 | end 386 | 387 | it 'should create selinux source policy file' do 388 | expect(chef_run).to render_file('/tmp/ssh-hardening-file-cache/ssh-hardening/ssh_password.te') 389 | end 390 | 391 | context 'when selinux is disabled or policy is installed' do 392 | let(:selinux_disabled_or_policy_removed) { true } 393 | 394 | it 'should not install the policy' do 395 | expect(chef_run).not_to run_bash('build selinux package and install it') 396 | end 397 | end 398 | 399 | context 'when selinux is enabled and policy is not installed' do 400 | let(:selinux_disabled_or_policy_removed) { false } 401 | 402 | it 'should install the policy' do 403 | expect(chef_run).to run_bash('build selinux package and install it') 404 | end 405 | end 406 | end 407 | end 408 | end 409 | 410 | it 'should not build own DH primes per default' do 411 | expect(chef_run).not_to run_bash('build own primes for DH') 412 | end 413 | 414 | describe 'DH primes handling' do 415 | let(:chef_run) do 416 | ChefSpec::SoloRunner.new.converge(described_recipe) 417 | end 418 | 419 | context 'when there are no small primes' do 420 | let(:dh_primes_ok) { true } 421 | 422 | it 'should not remove small primes from DH moduli' do 423 | expect(chef_run).not_to run_ruby_block('remove small primes from DH moduli') 424 | end 425 | end 426 | 427 | context 'when there are small primes present' do 428 | let(:dh_primes_ok) { false } 429 | 430 | it 'should invoke small primes from DH module' do 431 | expect(chef_run).to run_ruby_block('remove small primes from DH moduli') 432 | end 433 | end 434 | end 435 | 436 | describe 'debian banner' do 437 | cached(:chef_run) do 438 | ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '16.04').converge(described_recipe) 439 | end 440 | 441 | it 'disables the debian banner' do 442 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 443 | with_content(/DebianBanner no/) 444 | end 445 | 446 | context 'with enabled debian banner' do 447 | cached(:chef_run) do 448 | ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '16.04') do |node| 449 | node.normal['ssh-hardening']['ssh']['server']['os_banner'] = true 450 | end.converge(described_recipe) 451 | end 452 | 453 | it 'uses the enabled debian banner' do 454 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 455 | with_content(/DebianBanner yes/) 456 | end 457 | end 458 | 459 | context 'with centos as platform' do 460 | before do 461 | stub_command('getenforce | grep -vq Disabled && semodule -l | grep -q ssh_password').and_return(true) 462 | end 463 | 464 | cached(:chef_run) do 465 | ChefSpec::SoloRunner.new(platform: 'centos', version: '7.5.1804') do |node| 466 | node.normal['ssh-hardening']['ssh']['server']['os_banner'] = true 467 | end.converge(described_recipe) 468 | end 469 | 470 | it 'does not have the debian banner option' do 471 | expect(chef_run).not_to render_file('/etc/ssh/sshd_config'). 472 | with_content(/DebianBanner/) 473 | end 474 | end 475 | end 476 | 477 | describe 'extra configuration values' do 478 | context 'without custom extra config value' do 479 | cached(:chef_run) do 480 | ChefSpec::SoloRunner.new.converge(described_recipe) 481 | end 482 | 483 | it 'does not have any extra config options' do 484 | expect(chef_run).to render_file('/etc/ssh/sshd_config') 485 | expect(chef_run).not_to render_file('/etc/ssh/sshd_config'). 486 | with_content(/^# Extra Configuration Options/) 487 | end 488 | end 489 | 490 | context 'with custom extra config value' do 491 | cached(:chef_run) do 492 | ChefSpec::SoloRunner.new do |node| 493 | node.normal['ssh-hardening']['ssh']['server']['extras']['#ExtraConfig'] = 'Value' 494 | end.converge(described_recipe) 495 | end 496 | 497 | it 'uses the extra config attributes' do 498 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content(/^# Extra Configuration Options/) 499 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content(/^#ExtraConfig Value/) 500 | end 501 | end 502 | end 503 | 504 | describe 'match configuration blocks' do 505 | context 'without custom extra config value' do 506 | cached(:chef_run) do 507 | ChefSpec::SoloRunner.new.converge(described_recipe) 508 | end 509 | 510 | it 'does not have any match config blocks' do 511 | expect(chef_run).to render_file('/etc/ssh/sshd_config') 512 | expect(chef_run).not_to render_file('/etc/ssh/sshd_config'). 513 | with_content(/^# Match Configuration Blocks/) 514 | end 515 | end 516 | 517 | context 'with custom match config block value' do 518 | cached(:chef_run) do 519 | ChefSpec::SoloRunner.new do |node| 520 | node.normal['ssh-hardening']['ssh']['server']['match_blocks']['User root'] = <<~ROOT 521 | AuthorizedKeysFile .ssh/authorized_keys 522 | ROOT 523 | end.converge(described_recipe) 524 | end 525 | 526 | it 'uses the match config blocks' do 527 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content(/^# Match Configuration Blocks/) 528 | expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content(/^Match User root/) 529 | end 530 | end 531 | end 532 | 533 | it 'disables the challenge response authentication' do 534 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 535 | with_content(/ChallengeResponseAuthentication no/) 536 | end 537 | 538 | context 'with challenge response authentication enabled' do 539 | cached(:chef_run) do 540 | ChefSpec::SoloRunner.new do |node| 541 | node.normal['ssh-hardening']['ssh']['server']['challenge_response_authentication'] = true 542 | end.converge(described_recipe) 543 | end 544 | 545 | it 'enables the challenge response authentication' do 546 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 547 | with_content(/ChallengeResponseAuthentication yes/) 548 | end 549 | end 550 | 551 | it 'sets the login grace time to 30s' do 552 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 553 | with_content(/LoginGraceTime 30s/) 554 | end 555 | 556 | context 'with configured login grace time to 60s' do 557 | cached(:chef_run) do 558 | ChefSpec::SoloRunner.new do |node| 559 | node.normal['ssh-hardening']['ssh']['server']['login_grace_time'] = '60s' 560 | end.converge(described_recipe) 561 | end 562 | 563 | it 'sets the login grace time to 60s' do 564 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 565 | with_content(/LoginGraceTime 60s/) 566 | end 567 | end 568 | 569 | it 'sets the log level to verbose' do 570 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 571 | with_content('LogLevel VERBOSE') 572 | end 573 | 574 | context 'with log level set to debug' do 575 | cached(:chef_run) do 576 | ChefSpec::SoloRunner.new do |node| 577 | node.normal['ssh-hardening']['ssh']['server']['log_level'] = 'debug' 578 | end.converge(described_recipe) 579 | end 580 | 581 | it 'sets the log level to debug' do 582 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 583 | with_content('LogLevel DEBUG') 584 | end 585 | end 586 | 587 | it 'leaves deny users commented' do 588 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 589 | with_content(/#DenyUsers */) 590 | end 591 | 592 | it 'leaves allow users commented' do 593 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 594 | with_content(/#AllowUsers user1/) 595 | end 596 | 597 | it 'leaves deny groups commented' do 598 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 599 | with_content(/#DenyGroups */) 600 | end 601 | 602 | it 'leaves allow groups commented' do 603 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 604 | with_content(/#AllowGroups group1/) 605 | end 606 | 607 | context 'with attribute deny_users' do 608 | cached(:chef_run) do 609 | ChefSpec::SoloRunner.new do |node| 610 | node.normal['ssh-hardening']['ssh']['server']['deny_users'] = %w[someuser] 611 | end.converge(described_recipe) 612 | end 613 | 614 | it 'adds user to deny list' do 615 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 616 | with_content(/DenyUsers [^#]*\bsomeuser\b/) 617 | end 618 | end 619 | 620 | context 'with attribute deny_users mutiple' do 621 | cached(:chef_run) do 622 | ChefSpec::SoloRunner.new do |node| 623 | node.normal['ssh-hardening']['ssh']['server']['deny_users'] = %w[someuser otheruser] 624 | end.converge(described_recipe) 625 | end 626 | 627 | it 'adds users to deny list' do 628 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 629 | with_content(/DenyUsers [^#]*\bsomeuser otheruser\b/) 630 | end 631 | end 632 | 633 | context 'without attribute use_dns' do 634 | it 'leaves UseDNS commented' do 635 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 636 | with_content(/#UseDNS no/) 637 | end 638 | end 639 | 640 | context 'with attribute use_dns set to false' do 641 | cached(:chef_run) do 642 | ChefSpec::SoloRunner.new do |node| 643 | node.normal['ssh-hardening']['ssh']['server']['use_dns'] = false 644 | end.converge(described_recipe) 645 | end 646 | 647 | it 'sets UseDNS correctly' do 648 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 649 | with_content(/UseDNS no/) 650 | end 651 | end 652 | 653 | context 'with attribute use_dns set to true' do 654 | cached(:chef_run) do 655 | ChefSpec::SoloRunner.new do |node| 656 | node.normal['ssh-hardening']['ssh']['server']['use_dns'] = true 657 | end.converge(described_recipe) 658 | end 659 | 660 | it 'sets UseDNS correctly' do 661 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 662 | with_content(/UseDNS yes/) 663 | end 664 | end 665 | 666 | context 'without attribute ["sftp"]["enable"]' do 667 | it 'leaves SFTP Subsystem commented' do 668 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 669 | with_content(/^#Subsystem sftp/) 670 | end 671 | end 672 | 673 | context 'with attribute ["sftp"]["enable"] set to true' do 674 | cached(:chef_run) do 675 | ChefSpec::SoloRunner.new do |node| 676 | node.normal['ssh-hardening']['ssh']['server']['sftp']['enable'] = true 677 | end.converge(described_recipe) 678 | end 679 | 680 | it 'sets SFTP Subsystem correctly' do 681 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 682 | with_content(/^Subsystem sftp/) 683 | end 684 | end 685 | 686 | context 'with attribute ["sftp"]["enable"] set to true and ["sftp"]["group"] set to "testgroup"' do 687 | cached(:chef_run) do 688 | ChefSpec::SoloRunner.new do |node| 689 | node.normal['ssh-hardening']['ssh']['server']['sftp']['enable'] = true 690 | node.normal['ssh-hardening']['ssh']['server']['sftp']['group'] = 'testgroup' 691 | end.converge(described_recipe) 692 | end 693 | 694 | it 'sets the SFTP Group correctly' do 695 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 696 | with_content(/^Match Group testgroup$/) 697 | end 698 | end 699 | 700 | context 'with attribute ["sftp"]["enable"] set to true and ["sftp"]["chroot"] set to "/export/home/%u"' do 701 | cached(:chef_run) do 702 | ChefSpec::SoloRunner.new do |node| 703 | node.normal['ssh-hardening']['ssh']['server']['sftp']['enable'] = true 704 | node.normal['ssh-hardening']['ssh']['server']['sftp']['chroot'] = 'test_home_dir' 705 | end.converge(described_recipe) 706 | end 707 | 708 | it 'sets the SFTP chroot correctly' do 709 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 710 | with_content(/^[[:space:]]*ChrootDirectory test_home_dir$/) 711 | end 712 | end 713 | 714 | context 'with disabled IPv6' do 715 | cached(:chef_run) do 716 | ChefSpec::SoloRunner.new do |node| 717 | node.normal['ssh-hardening']['network']['ipv6']['enable'] = false 718 | end.converge(described_recipe) 719 | end 720 | 721 | it 'sets proper IPv4 ListenAdress' do 722 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 723 | with_content(/ListenAddress 0.0.0.0/) 724 | end 725 | end 726 | 727 | context 'with enabled IPv6' do 728 | cached(:chef_run) do 729 | ChefSpec::SoloRunner.new do |node| 730 | node.normal['ssh-hardening']['network']['ipv6']['enable'] = true 731 | end.converge(described_recipe) 732 | end 733 | 734 | it 'sets proper IPv4 and IPv6 ListenAdress' do 735 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 736 | with_content(/ListenAddress 0.0.0.0/). 737 | with_content(/ListenAddress ::/) 738 | end 739 | end 740 | 741 | context 'with empty accept_env attribute' do 742 | cached(:chef_run) do 743 | ChefSpec::SoloRunner.new do |node| 744 | node.normal['ssh-hardening']['ssh']['server']['accept_env'] = [] 745 | end.converge(described_recipe) 746 | end 747 | 748 | it 'will not accept any environment variables' do 749 | expect(chef_run).to_not render_file('/etc/ssh/sshd_config'). 750 | with_content(/AcceptEnv/) 751 | end 752 | end 753 | 754 | context 'with custom accept_env attribute' do 755 | cached(:chef_run) do 756 | ChefSpec::SoloRunner.new do |node| 757 | node.normal['ssh-hardening']['ssh']['server']['accept_env'] = %w[some environment variables] 758 | end.converge(described_recipe) 759 | end 760 | 761 | it 'uses the value of accept_env attribute' do 762 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 763 | with_content(/AcceptEnv some environment variables/) 764 | end 765 | end 766 | 767 | describe 'customized AuthorizedKeysFile option' do 768 | context 'without customized AuthorizedKeysFile' do 769 | cached(:chef_run) do 770 | ChefSpec::SoloRunner.new.converge(described_recipe) 771 | end 772 | 773 | it 'does not have AuthorizedKeysFile configured' do 774 | expect(chef_run).not_to render_file('/etc/ssh/sshd_config'). 775 | with_content(/^[[:space:]]*AuthorizedKeysFile/) 776 | end 777 | end 778 | 779 | context 'with customized global AuthorizedKeysFile' do 780 | cached(:chef_run) do 781 | ChefSpec::SoloRunner.new do |node| 782 | node.normal['ssh-hardening']['ssh']['server']['authorized_keys_path'] = '/some/authorizedkeysfile' 783 | end.converge(described_recipe) 784 | end 785 | 786 | it 'has AuthorizedKeysFile configured' do 787 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 788 | with_content('AuthorizedKeysFile /some/authorizedkeysfile') 789 | end 790 | end 791 | 792 | context 'with customized sftponly AuthorizedKeysFile' do 793 | cached(:chef_run) do 794 | ChefSpec::SoloRunner.new do |node| 795 | node.normal['ssh-hardening']['ssh']['server']['sftp']['enable'] = true 796 | node.normal['ssh-hardening']['ssh']['server']['sftp']['authorized_keys_path'] = '/some/authorizedkeysfile' 797 | end.converge(described_recipe) 798 | end 799 | 800 | it 'has AuthorizedKeysFile configured' do 801 | expect(chef_run).to render_file('/etc/ssh/sshd_config'). 802 | with_content('AuthorizedKeysFile /some/authorizedkeysfile') 803 | end 804 | end 805 | end 806 | end 807 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v2.9.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.9.0) (2019-11-21) 4 | 5 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.8.0...v2.9.0) 6 | 7 | **Merged pull requests:** 8 | 9 | - CentOS 8: proper selinux package naming [\#223](https://github.com/dev-sec/chef-ssh-hardening/pull/223) ([artem-sidorenko](https://github.com/artem-sidorenko)) 10 | - CI: enable testing on centos-8 [\#222](https://github.com/dev-sec/chef-ssh-hardening/pull/222) ([artem-sidorenko](https://github.com/artem-sidorenko)) 11 | - Allow to specify an alternate AuthorizedKeysFile inside the Match block [\#214](https://github.com/dev-sec/chef-ssh-hardening/pull/214) ([dud225](https://github.com/dud225)) 12 | 13 | ## [v2.8.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.8.0) (2019-07-17) 14 | 15 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.7.0...v2.8.0) 16 | 17 | **Merged pull requests:** 18 | 19 | - Support of custom match configuration blocks [\#221](https://github.com/dev-sec/chef-ssh-hardening/pull/221) ([artem-sidorenko](https://github.com/artem-sidorenko)) 20 | - Enable CI testing of Debian 10 [\#220](https://github.com/dev-sec/chef-ssh-hardening/pull/220) ([artem-sidorenko](https://github.com/artem-sidorenko)) 21 | - Updating supported fedora versions and include Fedora 30 [\#219](https://github.com/dev-sec/chef-ssh-hardening/pull/219) ([artem-sidorenko](https://github.com/artem-sidorenko)) 22 | - Removal of Ubuntu 14.04 because of EOL [\#218](https://github.com/dev-sec/chef-ssh-hardening/pull/218) ([artem-sidorenko](https://github.com/artem-sidorenko)) 23 | - Switching testing to chef 14&15 [\#217](https://github.com/dev-sec/chef-ssh-hardening/pull/217) ([artem-sidorenko](https://github.com/artem-sidorenko)) 24 | - Tests: update of gems [\#213](https://github.com/dev-sec/chef-ssh-hardening/pull/213) ([artem-sidorenko](https://github.com/artem-sidorenko)) 25 | - Tests: try to use SoloRunner instead of ServerRunner [\#212](https://github.com/dev-sec/chef-ssh-hardening/pull/212) ([artem-sidorenko](https://github.com/artem-sidorenko)) 26 | 27 | ## [v2.7.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.7.0) (2018-11-21) 28 | 29 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.6.0...v2.7.0) 30 | 31 | **Merged pull requests:** 32 | 33 | - permit\_tunnel attribute - allow tun device forwarding [\#211](https://github.com/dev-sec/chef-ssh-hardening/pull/211) ([bobchaos](https://github.com/bobchaos)) 34 | - Update the CI settings [\#207](https://github.com/dev-sec/chef-ssh-hardening/pull/207) ([artem-sidorenko](https://github.com/artem-sidorenko)) 35 | - Update issue templates [\#206](https://github.com/dev-sec/chef-ssh-hardening/pull/206) ([rndmh3ro](https://github.com/rndmh3ro)) 36 | 37 | ## [v2.6.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.6.0) (2018-10-19) 38 | 39 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.5.0...v2.6.0) 40 | 41 | **Closed issues:** 42 | 43 | - Removal of deprecated options [\#202](https://github.com/dev-sec/chef-ssh-hardening/issues/202) 44 | 45 | **Merged pull requests:** 46 | 47 | - Update of badges in README [\#205](https://github.com/dev-sec/chef-ssh-hardening/pull/205) ([artem-sidorenko](https://github.com/artem-sidorenko)) 48 | - Removal of deprecated options for newer openssh versions [\#203](https://github.com/dev-sec/chef-ssh-hardening/pull/203) ([artem-sidorenko](https://github.com/artem-sidorenko)) 49 | 50 | ## [v2.5.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.5.0) (2018-10-10) 51 | 52 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.4.0...v2.5.0) 53 | 54 | **Closed issues:** 55 | 56 | - Change log level for SFTP Subsystem [\#199](https://github.com/dev-sec/chef-ssh-hardening/issues/199) 57 | 58 | **Merged pull requests:** 59 | 60 | - CI fix: pin cucumber 3 [\#201](https://github.com/dev-sec/chef-ssh-hardening/pull/201) ([artem-sidorenko](https://github.com/artem-sidorenko)) 61 | - Add attribute for sftp subsystem logging [\#200](https://github.com/dev-sec/chef-ssh-hardening/pull/200) ([rediculum](https://github.com/rediculum)) 62 | 63 | ## [v2.4.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.4.0) (2018-08-01) 64 | 65 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.3.1...v2.4.0) 66 | 67 | **Closed issues:** 68 | 69 | - Errors on Ubuntu 18.04 [\#195](https://github.com/dev-sec/chef-ssh-hardening/issues/195) 70 | 71 | **Merged pull requests:** 72 | 73 | - Avoid some deprecated options for OpenSSH \>=7.6 [\#198](https://github.com/dev-sec/chef-ssh-hardening/pull/198) ([artem-sidorenko](https://github.com/artem-sidorenko)) 74 | - Update of tests and supported distros and chef versions [\#197](https://github.com/dev-sec/chef-ssh-hardening/pull/197) ([artem-sidorenko](https://github.com/artem-sidorenko)) 75 | - Update of CI and test environment [\#196](https://github.com/dev-sec/chef-ssh-hardening/pull/196) ([artem-sidorenko](https://github.com/artem-sidorenko)) 76 | - Make ForwardAgent configurable for Client Configuration [\#193](https://github.com/dev-sec/chef-ssh-hardening/pull/193) ([kabakakao](https://github.com/kabakakao)) 77 | - minor update to the template [\#192](https://github.com/dev-sec/chef-ssh-hardening/pull/192) ([crashdummymch](https://github.com/crashdummymch)) 78 | - amazonlinux support [\#188](https://github.com/dev-sec/chef-ssh-hardening/pull/188) ([chris-rock](https://github.com/chris-rock)) 79 | 80 | ## [v2.3.1](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.3.1) (2018-02-13) 81 | 82 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.3.0...v2.3.1) 83 | 84 | **Merged pull requests:** 85 | 86 | - Modified the client\_alive\_interval default to 300 [\#187](https://github.com/dev-sec/chef-ssh-hardening/pull/187) ([iennae](https://github.com/iennae)) 87 | 88 | ## [v2.3.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.3.0) (2017-12-19) 89 | 90 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.2.1...v2.3.0) 91 | 92 | **Closed issues:** 93 | 94 | - remove ripemd from MAC list [\#185](https://github.com/dev-sec/chef-ssh-hardening/issues/185) 95 | - allowtcpforwarding with sftp enabled is declared twice [\#182](https://github.com/dev-sec/chef-ssh-hardening/issues/182) 96 | 97 | **Merged pull requests:** 98 | 99 | - remove ripemd from MAC list [\#186](https://github.com/dev-sec/chef-ssh-hardening/pull/186) ([atomic111](https://github.com/atomic111)) 100 | - Allow password authentication for sftp [\#184](https://github.com/dev-sec/chef-ssh-hardening/pull/184) ([avanier](https://github.com/avanier)) 101 | - Fix Extra Configuration [\#183](https://github.com/dev-sec/chef-ssh-hardening/pull/183) ([bdwyertech](https://github.com/bdwyertech)) 102 | 103 | ## [v2.2.1](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.2.1) (2017-08-22) 104 | 105 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.2.0...v2.2.1) 106 | 107 | **Closed issues:** 108 | 109 | - The cookbooks fails on Amazon Linux. [\#180](https://github.com/dev-sec/chef-ssh-hardening/issues/180) 110 | 111 | **Merged pull requests:** 112 | 113 | - Fix to Issue \#180. Cookbook fails on Amazon Linux [\#181](https://github.com/dev-sec/chef-ssh-hardening/pull/181) ([jonasduarte](https://github.com/jonasduarte)) 114 | 115 | ## [v2.2.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.2.0) (2017-06-18) 116 | 117 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.1.0...v2.2.0) 118 | 119 | **Closed issues:** 120 | 121 | - Issues on OpenSuse Leap 42.2 [\#177](https://github.com/dev-sec/chef-ssh-hardening/issues/177) 122 | - Chef 13 support [\#174](https://github.com/dev-sec/chef-ssh-hardening/issues/174) 123 | 124 | **Merged pull requests:** 125 | 126 | - Running rubocop in the 2.1 mode [\#179](https://github.com/dev-sec/chef-ssh-hardening/pull/179) ([artem-sidorenko](https://github.com/artem-sidorenko)) 127 | - CI: update to ruby 2.4.1 and gem update [\#178](https://github.com/dev-sec/chef-ssh-hardening/pull/178) ([artem-sidorenko](https://github.com/artem-sidorenko)) 128 | - CI, Harmonization of tests, Testing of Chef 13 and Chef 12 [\#176](https://github.com/dev-sec/chef-ssh-hardening/pull/176) ([artem-sidorenko](https://github.com/artem-sidorenko)) 129 | - CI: removal of EOL distros from testing and support [\#175](https://github.com/dev-sec/chef-ssh-hardening/pull/175) ([artem-sidorenko](https://github.com/artem-sidorenko)) 130 | 131 | ## [v2.1.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.1.0) (2017-04-19) 132 | 133 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v2.0.0...v2.1.0) 134 | 135 | **Implemented enhancements:** 136 | 137 | - Suse support missing in metadata [\#170](https://github.com/dev-sec/chef-ssh-hardening/issues/170) 138 | 139 | **Merged pull requests:** 140 | 141 | - Add Support for Extra Configuration Options [\#173](https://github.com/dev-sec/chef-ssh-hardening/pull/173) ([bdwyertech](https://github.com/bdwyertech)) 142 | - Authorized keys custom path [\#172](https://github.com/dev-sec/chef-ssh-hardening/pull/172) ([lubomir-kacalek](https://github.com/lubomir-kacalek)) 143 | - Add suse to the supported list in metadata [\#171](https://github.com/dev-sec/chef-ssh-hardening/pull/171) ([artem-sidorenko](https://github.com/artem-sidorenko)) 144 | - Removal of apt/yum cookbooks from tests [\#169](https://github.com/dev-sec/chef-ssh-hardening/pull/169) ([artem-sidorenko](https://github.com/artem-sidorenko)) 145 | 146 | ## [v2.0.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v2.0.0) (2017-02-06) 147 | 148 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v1.3.0...v2.0.0) 149 | 150 | **Implemented enhancements:** 151 | 152 | - Send and Accept locale environment variables [\#167](https://github.com/dev-sec/chef-ssh-hardening/pull/167) ([mikemoate](https://github.com/mikemoate)) 153 | - Removal of DSA key from defaults [\#161](https://github.com/dev-sec/chef-ssh-hardening/pull/161) ([artem-sidorenko](https://github.com/artem-sidorenko)) 154 | - Allow log level configuration of sshd [\#159](https://github.com/dev-sec/chef-ssh-hardening/pull/159) ([artem-sidorenko](https://github.com/artem-sidorenko)) 155 | - Split the attributes to the client and server areas [\#150](https://github.com/dev-sec/chef-ssh-hardening/pull/150) ([artem-sidorenko](https://github.com/artem-sidorenko)) 156 | - Attribute namespace \['ssh-hardening'\] added [\#144](https://github.com/dev-sec/chef-ssh-hardening/pull/144) ([artem-sidorenko](https://github.com/artem-sidorenko)) 157 | - Add node attributes to override KEX, MAC and cipher values [\#141](https://github.com/dev-sec/chef-ssh-hardening/pull/141) ([bazbremner](https://github.com/bazbremner)) 158 | - Use different algorithms depending on the ssh version [\#166](https://github.com/dev-sec/chef-ssh-hardening/pull/166) ([artem-sidorenko](https://github.com/artem-sidorenko)) 159 | - Avoid small primes for DH and allow rebuild of DH primes [\#163](https://github.com/dev-sec/chef-ssh-hardening/pull/163) ([artem-sidorenko](https://github.com/artem-sidorenko)) 160 | - Switch UsePAM default to yes [\#157](https://github.com/dev-sec/chef-ssh-hardening/pull/157) ([artem-sidorenko](https://github.com/artem-sidorenko)) 161 | 162 | **Fixed bugs:** 163 | 164 | - IPv6 is not working still if its enabled [\#140](https://github.com/dev-sec/chef-ssh-hardening/issues/140) 165 | 166 | **Closed issues:** 167 | 168 | - Possibly missing locale handling [\#160](https://github.com/dev-sec/chef-ssh-hardening/issues/160) 169 | - Verify the current crypto settings [\#162](https://github.com/dev-sec/chef-ssh-hardening/issues/162) 170 | - Error message about DSA key on RHEL 7 [\#158](https://github.com/dev-sec/chef-ssh-hardening/issues/158) 171 | - Attributes should be in the own namespace ssh-hardening [\#142](https://github.com/dev-sec/chef-ssh-hardening/issues/142) 172 | - Move entire crypto parameter configuration in tests to the centralized place [\#137](https://github.com/dev-sec/chef-ssh-hardening/issues/137) 173 | - Move UsePrivilegeSeparation.get to the new library [\#136](https://github.com/dev-sec/chef-ssh-hardening/issues/136) 174 | - Release 2.0.0 [\#133](https://github.com/dev-sec/chef-ssh-hardening/issues/133) 175 | - configure log level [\#117](https://github.com/dev-sec/chef-ssh-hardening/issues/117) 176 | - UsePAM should probably default to yes on Red Hat Linux 7 [\#96](https://github.com/dev-sec/chef-ssh-hardening/issues/96) 177 | - refactor library kex and cipher implementation [\#87](https://github.com/dev-sec/chef-ssh-hardening/issues/87) 178 | - prohibit use of weak dh moduli [\#65](https://github.com/dev-sec/chef-ssh-hardening/issues/65) 179 | - Harmonize API [\#53](https://github.com/dev-sec/chef-ssh-hardening/issues/53) 180 | - SSH rootkey configuration is too open [\#16](https://github.com/dev-sec/chef-ssh-hardening/issues/16) 181 | 182 | **Merged pull requests:** 183 | 184 | - Add oracle bento boxes to vagrant testing [\#168](https://github.com/dev-sec/chef-ssh-hardening/pull/168) ([artem-sidorenko](https://github.com/artem-sidorenko)) 185 | - Project data for changelog generator [\#164](https://github.com/dev-sec/chef-ssh-hardening/pull/164) ([artem-sidorenko](https://github.com/artem-sidorenko)) 186 | - Improve the docs on the attribute overriding [\#156](https://github.com/dev-sec/chef-ssh-hardening/pull/156) ([artem-sidorenko](https://github.com/artem-sidorenko)) 187 | - Tests for GH-131 and GH-132 [\#155](https://github.com/dev-sec/chef-ssh-hardening/pull/155) ([artem-sidorenko](https://github.com/artem-sidorenko)) 188 | - Update attribute documentation in README [\#154](https://github.com/dev-sec/chef-ssh-hardening/pull/154) ([artem-sidorenko](https://github.com/artem-sidorenko)) 189 | - Fix the broken master [\#153](https://github.com/dev-sec/chef-ssh-hardening/pull/153) ([artem-sidorenko](https://github.com/artem-sidorenko)) 190 | - Fixing the broken links in docs [\#152](https://github.com/dev-sec/chef-ssh-hardening/pull/152) ([artem-sidorenko](https://github.com/artem-sidorenko)) 191 | - Some tests for attributes of last merged PRs [\#151](https://github.com/dev-sec/chef-ssh-hardening/pull/151) ([artem-sidorenko](https://github.com/artem-sidorenko)) 192 | - Get rid of chefspec/fauxhai warnings in the unit tests [\#149](https://github.com/dev-sec/chef-ssh-hardening/pull/149) ([artem-sidorenko](https://github.com/artem-sidorenko)) 193 | - Bugfix: sshd listens on IPv6 interface if enabled [\#148](https://github.com/dev-sec/chef-ssh-hardening/pull/148) ([artem-sidorenko](https://github.com/artem-sidorenko)) 194 | - Update and cleanup of Gemfile [\#147](https://github.com/dev-sec/chef-ssh-hardening/pull/147) ([artem-sidorenko](https://github.com/artem-sidorenko)) 195 | - Cleanup of some unmaintained docs/files [\#146](https://github.com/dev-sec/chef-ssh-hardening/pull/146) ([artem-sidorenko](https://github.com/artem-sidorenko)) 196 | - Removal of deprecated attributes [\#145](https://github.com/dev-sec/chef-ssh-hardening/pull/145) ([artem-sidorenko](https://github.com/artem-sidorenko)) 197 | - Removal of deprecated authorized\_keys handling [\#143](https://github.com/dev-sec/chef-ssh-hardening/pull/143) ([artem-sidorenko](https://github.com/artem-sidorenko)) 198 | - Refactoring of library to simplify the kex/cipher handling [\#134](https://github.com/dev-sec/chef-ssh-hardening/pull/134) ([artem-sidorenko](https://github.com/artem-sidorenko)) 199 | 200 | ## [v1.3.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v1.3.0) (2016-11-23) 201 | 202 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v1.2.1...v1.3.0) 203 | 204 | **Implemented enhancements:** 205 | 206 | - Support for OpenSuse Leap, new enterprise distro of SUSE [\#128](https://github.com/dev-sec/chef-ssh-hardening/pull/128) ([artem-sidorenko](https://github.com/artem-sidorenko)) 207 | - Avoid duplicate resource names because of warnings [\#127](https://github.com/dev-sec/chef-ssh-hardening/pull/127) ([artem-sidorenko](https://github.com/artem-sidorenko)) 208 | 209 | **Closed issues:** 210 | 211 | - Allow to configure ChallengeResponseAuthentication \(currently it's hardcoded to no\) [\#125](https://github.com/dev-sec/chef-ssh-hardening/issues/125) 212 | - Make LoginGraceTime configurable [\#116](https://github.com/dev-sec/chef-ssh-hardening/issues/116) 213 | - Allow to configure MaxAuthTries [\#100](https://github.com/dev-sec/chef-ssh-hardening/issues/100) 214 | 215 | **Merged pull requests:** 216 | 217 | - Fixing metadata as supermarket API expects a float [\#139](https://github.com/dev-sec/chef-ssh-hardening/pull/139) ([artem-sidorenko](https://github.com/artem-sidorenko)) 218 | - Distro information for supermarket [\#138](https://github.com/dev-sec/chef-ssh-hardening/pull/138) ([artem-sidorenko](https://github.com/artem-sidorenko)) 219 | - Allow login grace time to be configurable [\#132](https://github.com/dev-sec/chef-ssh-hardening/pull/132) ([artem-sidorenko](https://github.com/artem-sidorenko)) 220 | - Allow to configure ChallengeResponseAuthentication [\#131](https://github.com/dev-sec/chef-ssh-hardening/pull/131) ([artem-sidorenko](https://github.com/artem-sidorenko)) 221 | - Configurable SSH Banner File [\#130](https://github.com/dev-sec/chef-ssh-hardening/pull/130) ([sidxz](https://github.com/sidxz)) 222 | - Update kitchen vagrant configuration [\#129](https://github.com/dev-sec/chef-ssh-hardening/pull/129) ([artem-sidorenko](https://github.com/artem-sidorenko)) 223 | - Parameterise Banner and DebianBanner as attributes [\#126](https://github.com/dev-sec/chef-ssh-hardening/pull/126) ([tsenart](https://github.com/tsenart)) 224 | - Update Rubocop, Foodcritic, and Chefspec coverage [\#124](https://github.com/dev-sec/chef-ssh-hardening/pull/124) ([shortdudey123](https://github.com/shortdudey123)) 225 | 226 | ## [v1.2.1](https://github.com/dev-sec/chef-ssh-hardening/tree/v1.2.1) (2016-09-25) 227 | 228 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v1.2.0...v1.2.1) 229 | 230 | **Implemented enhancements:** 231 | 232 | - add suse and opensuse support [\#122](https://github.com/dev-sec/chef-ssh-hardening/pull/122) ([chris-rock](https://github.com/chris-rock)) 233 | - activate fedora integration tests in travis [\#120](https://github.com/dev-sec/chef-ssh-hardening/pull/120) ([chris-rock](https://github.com/chris-rock)) 234 | 235 | **Merged pull requests:** 236 | 237 | - Fix deprecation warnings [\#123](https://github.com/dev-sec/chef-ssh-hardening/pull/123) ([operatingops](https://github.com/operatingops)) 238 | - Use bracket syntax in attributes/default.rb [\#121](https://github.com/dev-sec/chef-ssh-hardening/pull/121) ([aried3r](https://github.com/aried3r)) 239 | - Use new ciphers, kex, macs and priv separation sandbox for redhat family 7 [\#119](https://github.com/dev-sec/chef-ssh-hardening/pull/119) ([atomic111](https://github.com/atomic111)) 240 | - change hardening-io to dev-sec domain for build status and code coverage [\#118](https://github.com/dev-sec/chef-ssh-hardening/pull/118) ([atomic111](https://github.com/atomic111)) 241 | 242 | ## [v1.2.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v1.2.0) (2016-05-29) 243 | 244 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v1.1.0...v1.2.0) 245 | 246 | **Implemented enhancements:** 247 | 248 | - add changelog generator [\#104](https://github.com/dev-sec/chef-ssh-hardening/pull/104) ([chris-rock](https://github.com/chris-rock)) 249 | 250 | **Closed issues:** 251 | 252 | - SFTP not configurable [\#110](https://github.com/dev-sec/chef-ssh-hardening/issues/110) 253 | - default to 'UseRoaming no' [\#109](https://github.com/dev-sec/chef-ssh-hardening/issues/109) 254 | - Consider using blank config\_disclaimer by default [\#94](https://github.com/dev-sec/chef-ssh-hardening/issues/94) 255 | 256 | **Merged pull requests:** 257 | 258 | - Document MaxAuthTries and MaxSessions added in 66e7ebfd [\#115](https://github.com/dev-sec/chef-ssh-hardening/pull/115) ([bazbremner](https://github.com/bazbremner)) 259 | - Use new InSpec integration tests [\#114](https://github.com/dev-sec/chef-ssh-hardening/pull/114) ([atomic111](https://github.com/atomic111)) 260 | - Add conditional to cover systemd in Ubuntu 15.04+ [\#112](https://github.com/dev-sec/chef-ssh-hardening/pull/112) ([elijah](https://github.com/elijah)) 261 | - Feature/sftp [\#111](https://github.com/dev-sec/chef-ssh-hardening/pull/111) ([jmara](https://github.com/jmara)) 262 | - Disable experimental client roaming [\#108](https://github.com/dev-sec/chef-ssh-hardening/pull/108) ([ascendantlogic](https://github.com/ascendantlogic)) 263 | - Made MaxAuthTries and MaxSessions configurable [\#107](https://github.com/dev-sec/chef-ssh-hardening/pull/107) ([runningman84](https://github.com/runningman84)) 264 | - added inspec support \(kitchen.yml and Gemfile\) [\#106](https://github.com/dev-sec/chef-ssh-hardening/pull/106) ([atomic111](https://github.com/atomic111)) 265 | - Apply PasswordAuthentication attribute to SSH [\#105](https://github.com/dev-sec/chef-ssh-hardening/pull/105) ([SteveLowe](https://github.com/SteveLowe)) 266 | - Configurable PasswordAuthentication [\#102](https://github.com/dev-sec/chef-ssh-hardening/pull/102) ([sumitgoelpw](https://github.com/sumitgoelpw)) 267 | - x11 forwarding should be configurable like tcp and agent forwarding [\#99](https://github.com/dev-sec/chef-ssh-hardening/pull/99) ([patcon](https://github.com/patcon)) 268 | - Correct recipe names in the README [\#98](https://github.com/dev-sec/chef-ssh-hardening/pull/98) ([michaelklishin](https://github.com/michaelklishin)) 269 | - update common kitchen.yml platforms [\#97](https://github.com/dev-sec/chef-ssh-hardening/pull/97) ([chris-rock](https://github.com/chris-rock)) 270 | - fixes \#94 [\#95](https://github.com/dev-sec/chef-ssh-hardening/pull/95) ([chris-rock](https://github.com/chris-rock)) 271 | - remove old slack notification [\#92](https://github.com/dev-sec/chef-ssh-hardening/pull/92) ([chris-rock](https://github.com/chris-rock)) 272 | - update common Gemfile for chef11+12 [\#91](https://github.com/dev-sec/chef-ssh-hardening/pull/91) ([arlimus](https://github.com/arlimus)) 273 | - common files: centos7 + rubocop [\#90](https://github.com/dev-sec/chef-ssh-hardening/pull/90) ([arlimus](https://github.com/arlimus)) 274 | - improve metadata description [\#88](https://github.com/dev-sec/chef-ssh-hardening/pull/88) ([chris-rock](https://github.com/chris-rock)) 275 | 276 | ## [v1.1.0](https://github.com/dev-sec/chef-ssh-hardening/tree/v1.1.0) (2015-04-28) 277 | 278 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v1.0.3...v1.1.0) 279 | 280 | **Closed issues:** 281 | 282 | - Use new "UseDNS" openssh default [\#81](https://github.com/dev-sec/chef-ssh-hardening/issues/81) 283 | - UseDNS no [\#79](https://github.com/dev-sec/chef-ssh-hardening/issues/79) 284 | - Debian 8.0 \(Jessie\) ships with OpenSSH 6.7p1, enable modern algos [\#77](https://github.com/dev-sec/chef-ssh-hardening/issues/77) 285 | - Allow management of allow/deny users [\#75](https://github.com/dev-sec/chef-ssh-hardening/issues/75) 286 | - update tutorial.md [\#55](https://github.com/dev-sec/chef-ssh-hardening/issues/55) 287 | 288 | **Merged pull requests:** 289 | 290 | - add Debian 8 to local test-kitchen [\#84](https://github.com/dev-sec/chef-ssh-hardening/pull/84) ([chris-rock](https://github.com/chris-rock)) 291 | - Modern alogs for Jessie [\#83](https://github.com/dev-sec/chef-ssh-hardening/pull/83) ([Rockstar04](https://github.com/Rockstar04)) 292 | - Update README and use OpenSSH defaults for UseDNS [\#82](https://github.com/dev-sec/chef-ssh-hardening/pull/82) ([aried3r](https://github.com/aried3r)) 293 | - Make UseDNS configurable [\#80](https://github.com/dev-sec/chef-ssh-hardening/pull/80) ([aried3r](https://github.com/aried3r)) 294 | - update common readme badges [\#78](https://github.com/dev-sec/chef-ssh-hardening/pull/78) ([arlimus](https://github.com/arlimus)) 295 | - Allow deny users to be managed from attributes [\#76](https://github.com/dev-sec/chef-ssh-hardening/pull/76) ([Rockstar04](https://github.com/Rockstar04)) 296 | - fix typo in opensshdconf.erb, remove trailing whitespace [\#74](https://github.com/dev-sec/chef-ssh-hardening/pull/74) ([zachallett](https://github.com/zachallett)) 297 | - bugfix: adjust travis to work with chef12/ruby2 [\#73](https://github.com/dev-sec/chef-ssh-hardening/pull/73) ([arlimus](https://github.com/arlimus)) 298 | - add privilege separation via sandbox mode for ssh \>= 5.9 [\#72](https://github.com/dev-sec/chef-ssh-hardening/pull/72) ([arlimus](https://github.com/arlimus)) 299 | - Adding attributes to enable printing the MOTD. [\#71](https://github.com/dev-sec/chef-ssh-hardening/pull/71) ([dmerrick](https://github.com/dmerrick)) 300 | 301 | ## [v1.0.3](https://github.com/dev-sec/chef-ssh-hardening/tree/v1.0.3) (2015-01-14) 302 | 303 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/v1.0.2...v1.0.3) 304 | 305 | **Closed issues:** 306 | 307 | - Suggestion: Don't populate /root/.ssh/authorized\_keys by default [\#69](https://github.com/dev-sec/chef-ssh-hardening/issues/69) 308 | - prefer etm MACs [\#66](https://github.com/dev-sec/chef-ssh-hardening/issues/66) 309 | - disable sha1-based key exchanges [\#64](https://github.com/dev-sec/chef-ssh-hardening/issues/64) 310 | 311 | **Merged pull requests:** 312 | 313 | - remove sha1 key-exchange mechanisms from default [\#70](https://github.com/dev-sec/chef-ssh-hardening/pull/70) ([arlimus](https://github.com/arlimus)) 314 | - reprioritize etm macs [\#68](https://github.com/dev-sec/chef-ssh-hardening/pull/68) ([arlimus](https://github.com/arlimus)) 315 | 316 | ## [v1.0.2](https://github.com/dev-sec/chef-ssh-hardening/tree/v1.0.2) (2015-01-12) 317 | 318 | [Full Changelog](https://github.com/dev-sec/chef-ssh-hardening/compare/21e5369c17790bf3096ad51f3d67dc998c74b632...v1.0.2) 319 | 320 | **Closed issues:** 321 | 322 | - release on supermarket [\#62](https://github.com/dev-sec/chef-ssh-hardening/issues/62) 323 | - host\_key\_files should not include ssh\_host\_ecdsa\_key on every host [\#61](https://github.com/dev-sec/chef-ssh-hardening/issues/61) 324 | - Protocol 1 options while SSH 2 is hard coded [\#57](https://github.com/dev-sec/chef-ssh-hardening/issues/57) 325 | - Configuration of root keys via databag and attributes [\#37](https://github.com/dev-sec/chef-ssh-hardening/issues/37) 326 | - Bad ciphers on debian 7.0 [\#25](https://github.com/dev-sec/chef-ssh-hardening/issues/25) 327 | - update ssh service on changes [\#24](https://github.com/dev-sec/chef-ssh-hardening/issues/24) 328 | 329 | **Merged pull requests:** 330 | 331 | - add back GCM cipher [\#67](https://github.com/dev-sec/chef-ssh-hardening/pull/67) ([arlimus](https://github.com/arlimus)) 332 | - updating common files [\#63](https://github.com/dev-sec/chef-ssh-hardening/pull/63) ([arlimus](https://github.com/arlimus)) 333 | - update to rubocop 0.27, exclude Berksfile [\#60](https://github.com/dev-sec/chef-ssh-hardening/pull/60) ([bkw](https://github.com/bkw)) 334 | - updating common files [\#59](https://github.com/dev-sec/chef-ssh-hardening/pull/59) ([arlimus](https://github.com/arlimus)) 335 | - remove options that only apply to SSH protocol version 1 [\#58](https://github.com/dev-sec/chef-ssh-hardening/pull/58) ([arlimus](https://github.com/arlimus)) 336 | - bring back support for chef-solo [\#56](https://github.com/dev-sec/chef-ssh-hardening/pull/56) ([bkw](https://github.com/bkw)) 337 | - add coverage dir to gitignore, add chefignore [\#54](https://github.com/dev-sec/chef-ssh-hardening/pull/54) ([bkw](https://github.com/bkw)) 338 | - Deprecate managing authorized\_keys for root via data bag [\#52](https://github.com/dev-sec/chef-ssh-hardening/pull/52) ([bkw](https://github.com/bkw)) 339 | - Add slack notifications [\#51](https://github.com/dev-sec/chef-ssh-hardening/pull/51) ([bkw](https://github.com/bkw)) 340 | - make users data bag optional [\#50](https://github.com/dev-sec/chef-ssh-hardening/pull/50) ([bkw](https://github.com/bkw)) 341 | - allow cbc, hmac and kex to be configured individually for client and server. [\#49](https://github.com/dev-sec/chef-ssh-hardening/pull/49) ([bkw](https://github.com/bkw)) 342 | - supply proper links for the badges [\#48](https://github.com/dev-sec/chef-ssh-hardening/pull/48) ([bkw](https://github.com/bkw)) 343 | - update travis builds to ruby 2.1.3 [\#47](https://github.com/dev-sec/chef-ssh-hardening/pull/47) ([bkw](https://github.com/bkw)) 344 | - add gymnasium badge for dependencies [\#46](https://github.com/dev-sec/chef-ssh-hardening/pull/46) ([bkw](https://github.com/bkw)) 345 | - update to chefspec 4.1.1 [\#45](https://github.com/dev-sec/chef-ssh-hardening/pull/45) ([bkw](https://github.com/bkw)) 346 | - Add badges [\#44](https://github.com/dev-sec/chef-ssh-hardening/pull/44) ([bkw](https://github.com/bkw)) 347 | - Add chef spec [\#43](https://github.com/dev-sec/chef-ssh-hardening/pull/43) ([bkw](https://github.com/bkw)) 348 | - Update rubocop [\#42](https://github.com/dev-sec/chef-ssh-hardening/pull/42) ([bkw](https://github.com/bkw)) 349 | - fix filenames in comments [\#41](https://github.com/dev-sec/chef-ssh-hardening/pull/41) ([bkw](https://github.com/bkw)) 350 | - updating common files [\#40](https://github.com/dev-sec/chef-ssh-hardening/pull/40) ([arlimus](https://github.com/arlimus)) 351 | - Chef Spec Tests [\#39](https://github.com/dev-sec/chef-ssh-hardening/pull/39) ([chris-rock](https://github.com/chris-rock)) 352 | - improvement: switch to site location in berkshelf [\#38](https://github.com/dev-sec/chef-ssh-hardening/pull/38) ([chris-rock](https://github.com/chris-rock)) 353 | - Lint [\#36](https://github.com/dev-sec/chef-ssh-hardening/pull/36) ([chris-rock](https://github.com/chris-rock)) 354 | - minor change to make md table in COMPLIANCE.md work [\#35](https://github.com/dev-sec/chef-ssh-hardening/pull/35) ([jklare](https://github.com/jklare)) 355 | - added info on crypto to readme [\#34](https://github.com/dev-sec/chef-ssh-hardening/pull/34) ([arlimus](https://github.com/arlimus)) 356 | - improvement: added faq on locked accounts to readme [\#33](https://github.com/dev-sec/chef-ssh-hardening/pull/33) ([arlimus](https://github.com/arlimus)) 357 | - updated kitchen images to current batch \(mysql-equivalent\) [\#32](https://github.com/dev-sec/chef-ssh-hardening/pull/32) ([arlimus](https://github.com/arlimus)) 358 | - add recipe to unlock user accounts [\#31](https://github.com/dev-sec/chef-ssh-hardening/pull/31) ([arlimus](https://github.com/arlimus)) 359 | - add pam option to readme [\#30](https://github.com/dev-sec/chef-ssh-hardening/pull/30) ([chris-rock](https://github.com/chris-rock)) 360 | - fixes \#24 [\#29](https://github.com/dev-sec/chef-ssh-hardening/pull/29) ([chris-rock](https://github.com/chris-rock)) 361 | - fix end keyword [\#28](https://github.com/dev-sec/chef-ssh-hardening/pull/28) ([arlimus](https://github.com/arlimus)) 362 | - Debian6fix [\#27](https://github.com/dev-sec/chef-ssh-hardening/pull/27) ([arlimus](https://github.com/arlimus)) 363 | - update kitchen tests for vagrant [\#26](https://github.com/dev-sec/chef-ssh-hardening/pull/26) ([arlimus](https://github.com/arlimus)) 364 | - update rubocop, add default rake task. fix errors with default task [\#23](https://github.com/dev-sec/chef-ssh-hardening/pull/23) ([ehaselwanter](https://github.com/ehaselwanter)) 365 | - update with common run\_all\_linters task [\#22](https://github.com/dev-sec/chef-ssh-hardening/pull/22) ([ehaselwanter](https://github.com/ehaselwanter)) 366 | - adapt to new tests [\#21](https://github.com/dev-sec/chef-ssh-hardening/pull/21) ([chris-rock](https://github.com/chris-rock)) 367 | - add openstack kitchen gem [\#20](https://github.com/dev-sec/chef-ssh-hardening/pull/20) ([chris-rock](https://github.com/chris-rock)) 368 | - rename package name attribute from ssl\* to ssh\* [\#19](https://github.com/dev-sec/chef-ssh-hardening/pull/19) ([bkw](https://github.com/bkw)) 369 | - passwordless users not able to log in [\#18](https://github.com/dev-sec/chef-ssh-hardening/pull/18) ([bkw](https://github.com/bkw)) 370 | - add utf8 header and use ruby 1.9 hash syntax [\#17](https://github.com/dev-sec/chef-ssh-hardening/pull/17) ([chris-rock](https://github.com/chris-rock)) 371 | - add Berksfile.lock Gemfile.lock to ignore list and remove it from tree [\#15](https://github.com/dev-sec/chef-ssh-hardening/pull/15) ([ehaselwanter](https://github.com/ehaselwanter)) 372 | - Typo in username of ssh connection [\#14](https://github.com/dev-sec/chef-ssh-hardening/pull/14) ([sirkkalap](https://github.com/sirkkalap)) 373 | - streamline .rubocop config [\#13](https://github.com/dev-sec/chef-ssh-hardening/pull/13) ([ehaselwanter](https://github.com/ehaselwanter)) 374 | - use the role from the integration test suite, not distinct recipes [\#12](https://github.com/dev-sec/chef-ssh-hardening/pull/12) ([ehaselwanter](https://github.com/ehaselwanter)) 375 | - fix rubocop violations [\#11](https://github.com/dev-sec/chef-ssh-hardening/pull/11) ([ehaselwanter](https://github.com/ehaselwanter)) 376 | - fix foodcritic violations [\#10](https://github.com/dev-sec/chef-ssh-hardening/pull/10) ([ehaselwanter](https://github.com/ehaselwanter)) 377 | - made TCP and Agent Forwarding configurable [\#9](https://github.com/dev-sec/chef-ssh-hardening/pull/9) ([atomic111](https://github.com/atomic111)) 378 | - be more forgiving and relax rubocop [\#8](https://github.com/dev-sec/chef-ssh-hardening/pull/8) ([ehaselwanter](https://github.com/ehaselwanter)) 379 | - add lint and spec infrastructure [\#7](https://github.com/dev-sec/chef-ssh-hardening/pull/7) ([ehaselwanter](https://github.com/ehaselwanter)) 380 | - integrate sharedtests [\#6](https://github.com/dev-sec/chef-ssh-hardening/pull/6) ([ehaselwanter](https://github.com/ehaselwanter)) 381 | - remove aes-gcm algos from Ciphers, because of http://www.openssh.com/txt/gcmrekey.adv [\#5](https://github.com/dev-sec/chef-ssh-hardening/pull/5) ([atomic111](https://github.com/atomic111)) 382 | - fix really old copy-n-paste error in readme [\#4](https://github.com/dev-sec/chef-ssh-hardening/pull/4) ([arlimus](https://github.com/arlimus)) 383 | - Contributing guide [\#3](https://github.com/dev-sec/chef-ssh-hardening/pull/3) ([arlimus](https://github.com/arlimus)) 384 | - added all kitchen test for ssh\_config + sshd\_config and added TUTORIAL.md [\#2](https://github.com/dev-sec/chef-ssh-hardening/pull/2) ([atomic111](https://github.com/atomic111)) 385 | - add license and improve styling [\#1](https://github.com/dev-sec/chef-ssh-hardening/pull/1) ([chris-rock](https://github.com/chris-rock)) 386 | 387 | 388 | 389 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 390 | --------------------------------------------------------------------------------