Skip to content

Commit 97b2f52

Browse files
authored
Merge pull request #166 from artem-sidorenko/alg-update
Use different algorithms depending on the ssh version
2 parents 5dfe85a + f9baa14 commit 97b2f52

File tree

6 files changed

+91
-4
lines changed

6 files changed

+91
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ override['ssh-hardening']['ssh']['server']['listen_to'] = node['ipaddress']
4646
* `['ssh-hardening']['ssh']['client']['remote_hosts']` - `[]` - one or more hosts, to which ssh-client can connect to.
4747
* `['ssh-hardening']['ssh']['client']['password_authentication']` - `false`. Set to `true` if password authentication should be enabled.
4848
* `['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.
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`)
4950
* `['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)
5051
* `['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).
5152
* `['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.

attributes/default.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
default['ssh-hardening']['ssh']['server']['dh_min_prime_size'] = 2048
7575
default['ssh-hardening']['ssh']['server']['dh_build_primes'] = false
7676
default['ssh-hardening']['ssh']['server']['dh_build_primes_size'] = 4096
77-
default['ssh-hardening']['ssh']['server']['host_key_files'] = ['/etc/ssh/ssh_host_rsa_key', '/etc/ssh/ssh_host_ecdsa_key']
77+
default['ssh-hardening']['ssh']['server']['host_key_files'] = nil
7878
default['ssh-hardening']['ssh']['server']['client_alive_interval'] = 600 # 10min
7979
default['ssh-hardening']['ssh']['server']['client_alive_count'] = 3 # ~> 3 x interval
8080
default['ssh-hardening']['ssh']['server']['allow_root_with_key'] = false

libraries/devsec_ssh.rb

+25-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ class Ssh # rubocop:disable Metrics/ClassLength
7070
5.3 => 'yes',
7171
5.9 => 'sandbox'
7272
}.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
7380

7481
class << self
7582
def get_server_privilege_separarion # rubocop:disable Style/AccessorMethodName
@@ -80,6 +87,14 @@ def get_server_privilege_separarion # rubocop:disable Style/AccessorMethodName
8087
ret
8188
end
8289

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+
8398
def get_client_macs(enable_weak = false)
8499
get_crypto_data(:macs, :client, enable_weak)
85100
end
@@ -176,7 +191,7 @@ def get_ssh_version(package)
176191
end
177192

178193
# Guess the version of ssh via OS matrix
179-
def guess_ssh_version
194+
def guess_ssh_version # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
180195
family = node['platform_family']
181196
platform = node['platform']
182197
version = node['platform_version'].to_f
@@ -188,11 +203,20 @@ def guess_ssh_version
188203
return 6.6 if version >= 14.04
189204
when 'debian'
190205
return 6.6 if version >= 8
206+
return 6.0 if version >= 7
191207
return 5.3 if version <= 6
192208
end
193209
when 'rhel'
194210
return 6.6 if version >= 7
195211
return 5.3 if version >= 6
212+
when 'fedora'
213+
return 7.3 if version >= 25
214+
return 7.2 if version >= 24
215+
when 'suse'
216+
case platform
217+
when 'opensuse'
218+
return 6.6 if version >= 13.2
219+
end
196220
end
197221
Chef::Log.info("Unknown platform #{node['platform']} with version #{node['platform_version']} and family #{node['platform_family']}. Assuming ssh version #{FALLBACK_SSH_VERSION}")
198222
FALLBACK_SSH_VERSION

recipes/server.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@
170170
mac: node['ssh-hardening']['ssh']['server']['mac'] || DevSec::Ssh.get_server_macs(node['ssh-hardening']['ssh']['server']['weak_hmac']),
171171
kex: node['ssh-hardening']['ssh']['server']['kex'] || DevSec::Ssh.get_server_kexs(node['ssh-hardening']['ssh']['server']['weak_kex']),
172172
cipher: node['ssh-hardening']['ssh']['server']['cipher'] || DevSec::Ssh.get_server_ciphers(node['ssh-hardening']['ssh']['server']['cbc_required']),
173-
use_priv_sep: node['ssh-hardening']['ssh']['use_privilege_separation'] || DevSec::Ssh.get_server_privilege_separarion
173+
use_priv_sep: node['ssh-hardening']['ssh']['use_privilege_separation'] || DevSec::Ssh.get_server_privilege_separarion,
174+
hostkeys: node['ssh-hardening']['ssh']['server']['host_key_files'] || DevSec::Ssh.get_server_algorithms.map { |alg| "/etc/ssh/ssh_host_#{alg}_key" }
174175
)
175176
notifies :restart, 'service[sshd]'
176177
end

spec/libraries/devsec_ssh_spec.rb

+61
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,46 @@ def self.debug(*); end
109109
end
110110
end
111111

112+
context 'when running on Fedora 25' do
113+
let(:family) { 'fedora' }
114+
let(:platform) { 'fedora' }
115+
let(:version) { '25' }
116+
117+
it 'should return ssh version 7.3' do
118+
expect(subject.send(:guess_ssh_version)).to eq 7.3
119+
end
120+
end
121+
122+
context 'when running on Fedora 24' do
123+
let(:family) { 'fedora' }
124+
let(:platform) { 'fedora' }
125+
let(:version) { '24' }
126+
127+
it 'should return ssh version 7.2' do
128+
expect(subject.send(:guess_ssh_version)).to eq 7.2
129+
end
130+
end
131+
132+
context 'when running on Opensuse 13.2' do
133+
let(:family) { 'suse' }
134+
let(:platform) { 'opensuse' }
135+
let(:version) { '13.2' }
136+
137+
it 'should return ssh version 6.6' do
138+
expect(subject.send(:guess_ssh_version)).to eq 6.6
139+
end
140+
end
141+
142+
context 'when running on Opensuse 42.1' do
143+
let(:family) { 'suse' }
144+
let(:platform) { 'opensuse' }
145+
let(:version) { '42.1' }
146+
147+
it 'should return ssh version 6.6' do
148+
expect(subject.send(:guess_ssh_version)).to eq 6.6
149+
end
150+
end
151+
112152
context 'when running on unknown platform' do
113153
let(:family) { 'unknown' }
114154
let(:platform) { 'unknown' }
@@ -202,6 +242,27 @@ def self.debug(*); end
202242
end
203243
end
204244

245+
describe 'get_server_algorithms' do
246+
DevSec::Ssh::HOSTKEY_ALGORITHMS.each do |openssh_version, conf_data|
247+
context "when openssh is >= #{openssh_version}" do
248+
before :each do
249+
# mock get_ssh_server_version. We test it somewhere else
250+
expect(subject).to receive(:get_ssh_server_version) { openssh_version }
251+
end
252+
253+
it "get the config value #{conf_data}" do
254+
expect(subject.get_server_algorithms).to eq conf_data
255+
end
256+
end
257+
end
258+
context 'when openssh has a totaly unsupported version, e.g. 3.0' do
259+
it 'should raise an exception' do
260+
expect(subject).to receive(:get_ssh_server_version) { 3.0 }
261+
expect { subject.get_server_algorithms }.to raise_exception(/Unsupported ssh version/)
262+
end
263+
end
264+
end
265+
205266
# Here we test the public functions:
206267
# get_[client|server]_[kexs|macs|ciphers]
207268
# In order to cover all possible combinations, we need a complex nested loops:-\

templates/default/opensshd.conf.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ ListenAddress <%= ssh_ip %>
3232
<% end %>
3333

3434
# List HostKeys here.
35-
<% Array(@node['ssh-hardening']['ssh']['server']['host_key_files']).each do |host_key_file| %>
35+
<% @hostkeys.each do |host_key_file| %>
3636
HostKey <%= host_key_file %> # Req 20
3737
<% end %>
3838

0 commit comments

Comments
 (0)