1
# Copyright 2016 Canonical Limited.
3
# This file is part of charm-helpers.
5
# charm-helpers is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3 as
7
# published by the Free Software Foundation.
9
# charm-helpers is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
19
from charmhelpers.core.hookenv import (
23
from charmhelpers.fetch import (
27
from charmhelpers.core.host import lsb_release
28
from charmhelpers.contrib.hardening.audits.file import (
32
from charmhelpers.contrib.hardening.ssh import TEMPLATES_DIR
33
from charmhelpers.contrib.hardening import utils
37
"""Get SSH hardening config audits.
39
:returns: dictionary of audits
41
audits = [SSHConfig(), SSHDConfig(), SSHConfigFileContentAudit(),
42
SSHDConfigFileContentAudit()]
46
class SSHConfigContext(object):
50
def get_macs(self, allow_weak_mac):
56
default = 'hmac-sha2-512,hmac-sha2-256,hmac-ripemd160'
57
macs = {'default': default,
58
'weak': default + ',hmac-sha1'}
60
default = ('hmac-sha2-512-etm@openssh.com,'
61
'hmac-sha2-256-etm@openssh.com,'
62
'hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,'
63
'hmac-sha2-512,hmac-sha2-256,hmac-ripemd160')
64
macs_66 = {'default': default,
65
'weak': default + ',hmac-sha1'}
67
# Use newer ciphers on Ubuntu Trusty and above
68
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
69
log("Detected Ubuntu 14.04 or newer, using new macs", level=DEBUG)
72
return macs[weak_macs]
74
def get_kexs(self, allow_weak_kex):
80
default = 'diffie-hellman-group-exchange-sha256'
81
weak = (default + ',diffie-hellman-group14-sha1,'
82
'diffie-hellman-group-exchange-sha1,'
83
'diffie-hellman-group1-sha1')
84
kex = {'default': default,
87
default = ('curve25519-sha256@libssh.org,'
88
'diffie-hellman-group-exchange-sha256')
89
weak = (default + ',diffie-hellman-group14-sha1,'
90
'diffie-hellman-group-exchange-sha1,'
91
'diffie-hellman-group1-sha1')
92
kex_66 = {'default': default,
95
# Use newer kex on Ubuntu Trusty and above
96
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
97
log('Detected Ubuntu 14.04 or newer, using new key exchange '
98
'algorithms', level=DEBUG)
103
def get_ciphers(self, cbc_required):
105
weak_ciphers = 'weak'
107
weak_ciphers = 'default'
109
default = 'aes256-ctr,aes192-ctr,aes128-ctr'
110
cipher = {'default': default,
111
'weak': default + 'aes256-cbc,aes192-cbc,aes128-cbc'}
113
default = ('chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,'
114
'aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr')
115
ciphers_66 = {'default': default,
116
'weak': default + ',aes256-cbc,aes192-cbc,aes128-cbc'}
118
# Use newer ciphers on ubuntu Trusty and above
119
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
120
log('Detected Ubuntu 14.04 or newer, using new ciphers',
124
return cipher[weak_ciphers]
127
settings = utils.get_settings('ssh')
128
if settings['common']['network_ipv6_enable']:
134
'addr_family': addr_family,
135
'remote_hosts': settings['common']['remote_hosts'],
136
'password_auth_allowed':
137
settings['client']['password_authentication'],
138
'ports': settings['common']['ports'],
139
'ciphers': self.get_ciphers(settings['client']['cbc_required']),
140
'macs': self.get_macs(settings['client']['weak_hmac']),
141
'kexs': self.get_kexs(settings['client']['weak_kex']),
142
'roaming': settings['client']['roaming'],
147
class SSHConfig(TemplatedFile):
149
path = '/etc/ssh/ssh_config'
150
super(SSHConfig, self).__init__(path=path,
151
template_dir=TEMPLATES_DIR,
152
context=SSHConfigContext(),
158
settings = utils.get_settings('ssh')
159
apt_update(fatal=True)
160
apt_install(settings['client']['package'])
161
if not os.path.exists('/etc/ssh'):
162
os.makedir('/etc/ssh')
163
# NOTE: don't recurse
164
utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755,
167
def post_write(self):
168
# NOTE: don't recurse
169
utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755,
173
class SSHDConfigContext(SSHConfigContext):
178
settings = utils.get_settings('ssh')
179
if settings['common']['network_ipv6_enable']:
185
'ssh_ip': settings['server']['listen_to'],
186
'password_auth_allowed':
187
settings['server']['password_authentication'],
188
'ports': settings['common']['ports'],
189
'addr_family': addr_family,
190
'ciphers': self.get_ciphers(settings['server']['cbc_required']),
191
'macs': self.get_macs(settings['server']['weak_hmac']),
192
'kexs': self.get_kexs(settings['server']['weak_kex']),
193
'host_key_files': settings['server']['host_key_files'],
194
'allow_root_with_key': settings['server']['allow_root_with_key'],
195
'password_authentication':
196
settings['server']['password_authentication'],
197
'use_priv_sep': settings['server']['use_privilege_separation'],
198
'use_pam': settings['server']['use_pam'],
199
'allow_x11_forwarding': settings['server']['allow_x11_forwarding'],
200
'print_motd': settings['server']['print_motd'],
201
'print_last_log': settings['server']['print_last_log'],
202
'client_alive_interval':
203
settings['server']['alive_interval'],
204
'client_alive_count': settings['server']['alive_count'],
205
'allow_tcp_forwarding': settings['server']['allow_tcp_forwarding'],
206
'allow_agent_forwarding':
207
settings['server']['allow_agent_forwarding'],
208
'deny_users': settings['server']['deny_users'],
209
'allow_users': settings['server']['allow_users'],
210
'deny_groups': settings['server']['deny_groups'],
211
'allow_groups': settings['server']['allow_groups'],
212
'use_dns': settings['server']['use_dns'],
213
'sftp_enable': settings['server']['sftp_enable'],
214
'sftp_group': settings['server']['sftp_group'],
215
'sftp_chroot': settings['server']['sftp_chroot'],
216
'max_auth_tries': settings['server']['max_auth_tries'],
217
'max_sessions': settings['server']['max_sessions'],
222
class SSHDConfig(TemplatedFile):
224
path = '/etc/ssh/sshd_config'
225
super(SSHDConfig, self).__init__(path=path,
226
template_dir=TEMPLATES_DIR,
227
context=SSHDConfigContext(),
231
service_actions=[{'service': 'ssh',
236
settings = utils.get_settings('ssh')
237
apt_update(fatal=True)
238
apt_install(settings['server']['package'])
239
if not os.path.exists('/etc/ssh'):
240
os.makedir('/etc/ssh')
241
# NOTE: don't recurse
242
utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755,
245
def post_write(self):
246
# NOTE: don't recurse
247
utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755,
251
class SSHConfigFileContentAudit(FileContentAudit):
253
self.path = '/etc/ssh/ssh_config'
254
super(SSHConfigFileContentAudit, self).__init__(self.path, {})
256
def is_compliant(self, *args, **kwargs):
259
settings = utils.get_settings('ssh')
261
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
262
if not settings['server']['weak_hmac']:
263
self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
265
self.pass_cases.append(r'^MACs.+,hmac-sha1$')
267
if settings['server']['weak_kex']:
268
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa
269
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa
270
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa
271
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa
273
self.pass_cases.append(r'^KexAlgorithms.+,diffie-hellman-group-exchange-sha256$') # noqa
274
self.fail_cases.append(r'^KexAlgorithms.*diffie-hellman-group14-sha1[,\s]?') # noqa
276
if settings['server']['cbc_required']:
277
self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
278
self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?')
279
self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
280
self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
282
self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
283
self.pass_cases.append(r'^Ciphers\schacha20-poly1305@openssh.com,.+') # noqa
284
self.pass_cases.append(r'^Ciphers\s.*aes128-ctr$')
285
self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
286
self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
288
if not settings['client']['weak_hmac']:
289
self.fail_cases.append(r'^MACs.+,hmac-sha1$')
291
self.pass_cases.append(r'^MACs.+,hmac-sha1$')
293
if settings['client']['weak_kex']:
294
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa
295
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa
296
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa
297
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa
299
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256$') # noqa
300
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa
301
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa
302
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa
304
if settings['client']['cbc_required']:
305
self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
306
self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?')
307
self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
308
self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
310
self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
311
self.pass_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?')
312
self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
313
self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
315
if settings['client']['roaming']:
316
self.pass_cases.append(r'^UseRoaming yes$')
318
self.fail_cases.append(r'^UseRoaming yes$')
320
return super(SSHConfigFileContentAudit, self).is_compliant(*args,
324
class SSHDConfigFileContentAudit(FileContentAudit):
326
self.path = '/etc/ssh/sshd_config'
327
super(SSHDConfigFileContentAudit, self).__init__(self.path, {})
329
def is_compliant(self, *args, **kwargs):
332
settings = utils.get_settings('ssh')
334
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
335
if not settings['server']['weak_hmac']:
336
self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
338
self.pass_cases.append(r'^MACs.+,hmac-sha1$')
340
if settings['server']['weak_kex']:
341
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa
342
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa
343
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa
344
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa
346
self.pass_cases.append(r'^KexAlgorithms.+,diffie-hellman-group-exchange-sha256$') # noqa
347
self.fail_cases.append(r'^KexAlgorithms.*diffie-hellman-group14-sha1[,\s]?') # noqa
349
if settings['server']['cbc_required']:
350
self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
351
self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?')
352
self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
353
self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
355
self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
356
self.pass_cases.append(r'^Ciphers\schacha20-poly1305@openssh.com,.+') # noqa
357
self.pass_cases.append(r'^Ciphers\s.*aes128-ctr$')
358
self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
359
self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
361
if not settings['server']['weak_hmac']:
362
self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
364
self.pass_cases.append(r'^MACs.+,hmac-sha1$')
366
if settings['server']['weak_kex']:
367
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa
368
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa
369
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa
370
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa
372
self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256$') # noqa
373
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa
374
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa
375
self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa
377
if settings['server']['cbc_required']:
378
self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
379
self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?')
380
self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
381
self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
383
self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?')
384
self.pass_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?')
385
self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?')
386
self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?')
388
if settings['server']['sftp_enable']:
389
self.pass_cases.append(r'^Subsystem\ssftp')
391
self.fail_cases.append(r'^Subsystem\ssftp')
393
return super(SSHDConfigFileContentAudit, self).is_compliant(*args,