~bbaqar/charms/trusty/neutron-api-plumgrid/temp

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/hardening/ssh/checks/config.py

  • Committer: bbaqar at plumgrid
  • Date: 2016-04-25 09:05:55 UTC
  • mfrom: (18.1.7 neutron-api-plumgrid)
  • Revision ID: bbaqar@plumgrid.com-20160425090555-yl82ba6bhwjqkwr9
Merge: Liberty/Mitaka support

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2016 Canonical Limited.
 
2
#
 
3
# This file is part of charm-helpers.
 
4
#
 
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.
 
8
#
 
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.
 
13
#
 
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/>.
 
16
 
 
17
import os
 
18
 
 
19
from charmhelpers.core.hookenv import (
 
20
    log,
 
21
    DEBUG,
 
22
)
 
23
from charmhelpers.fetch import (
 
24
    apt_install,
 
25
    apt_update,
 
26
)
 
27
from charmhelpers.core.host import lsb_release
 
28
from charmhelpers.contrib.hardening.audits.file import (
 
29
    TemplatedFile,
 
30
    FileContentAudit,
 
31
)
 
32
from charmhelpers.contrib.hardening.ssh import TEMPLATES_DIR
 
33
from charmhelpers.contrib.hardening import utils
 
34
 
 
35
 
 
36
def get_audits():
 
37
    """Get SSH hardening config audits.
 
38
 
 
39
    :returns:  dictionary of audits
 
40
    """
 
41
    audits = [SSHConfig(), SSHDConfig(), SSHConfigFileContentAudit(),
 
42
              SSHDConfigFileContentAudit()]
 
43
    return audits
 
44
 
 
45
 
 
46
class SSHConfigContext(object):
 
47
 
 
48
    type = 'client'
 
49
 
 
50
    def get_macs(self, allow_weak_mac):
 
51
        if allow_weak_mac:
 
52
            weak_macs = 'weak'
 
53
        else:
 
54
            weak_macs = 'default'
 
55
 
 
56
        default = 'hmac-sha2-512,hmac-sha2-256,hmac-ripemd160'
 
57
        macs = {'default': default,
 
58
                'weak': default + ',hmac-sha1'}
 
59
 
 
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'}
 
66
 
 
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)
 
70
            macs = macs_66
 
71
 
 
72
        return macs[weak_macs]
 
73
 
 
74
    def get_kexs(self, allow_weak_kex):
 
75
        if allow_weak_kex:
 
76
            weak_kex = 'weak'
 
77
        else:
 
78
            weak_kex = 'default'
 
79
 
 
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,
 
85
               'weak': weak}
 
86
 
 
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,
 
93
                  'weak': weak}
 
94
 
 
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)
 
99
            kex = kex_66
 
100
 
 
101
        return kex[weak_kex]
 
102
 
 
103
    def get_ciphers(self, cbc_required):
 
104
        if cbc_required:
 
105
            weak_ciphers = 'weak'
 
106
        else:
 
107
            weak_ciphers = 'default'
 
108
 
 
109
        default = 'aes256-ctr,aes192-ctr,aes128-ctr'
 
110
        cipher = {'default': default,
 
111
                  'weak': default + 'aes256-cbc,aes192-cbc,aes128-cbc'}
 
112
 
 
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'}
 
117
 
 
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',
 
121
                level=DEBUG)
 
122
            cipher = ciphers_66
 
123
 
 
124
        return cipher[weak_ciphers]
 
125
 
 
126
    def __call__(self):
 
127
        settings = utils.get_settings('ssh')
 
128
        if settings['common']['network_ipv6_enable']:
 
129
            addr_family = 'any'
 
130
        else:
 
131
            addr_family = 'inet'
 
132
 
 
133
        ctxt = {
 
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'],
 
143
        }
 
144
        return ctxt
 
145
 
 
146
 
 
147
class SSHConfig(TemplatedFile):
 
148
    def __init__(self):
 
149
        path = '/etc/ssh/ssh_config'
 
150
        super(SSHConfig, self).__init__(path=path,
 
151
                                        template_dir=TEMPLATES_DIR,
 
152
                                        context=SSHConfigContext(),
 
153
                                        user='root',
 
154
                                        group='root',
 
155
                                        mode=0o0644)
 
156
 
 
157
    def pre_write(self):
 
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,
 
165
                                     maxdepth=0)
 
166
 
 
167
    def post_write(self):
 
168
        # NOTE: don't recurse
 
169
        utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755,
 
170
                                 maxdepth=0)
 
171
 
 
172
 
 
173
class SSHDConfigContext(SSHConfigContext):
 
174
 
 
175
    type = 'server'
 
176
 
 
177
    def __call__(self):
 
178
        settings = utils.get_settings('ssh')
 
179
        if settings['common']['network_ipv6_enable']:
 
180
            addr_family = 'any'
 
181
        else:
 
182
            addr_family = 'inet'
 
183
 
 
184
        ctxt = {
 
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'],
 
218
        }
 
219
        return ctxt
 
220
 
 
221
 
 
222
class SSHDConfig(TemplatedFile):
 
223
    def __init__(self):
 
224
        path = '/etc/ssh/sshd_config'
 
225
        super(SSHDConfig, self).__init__(path=path,
 
226
                                         template_dir=TEMPLATES_DIR,
 
227
                                         context=SSHDConfigContext(),
 
228
                                         user='root',
 
229
                                         group='root',
 
230
                                         mode=0o0600,
 
231
                                         service_actions=[{'service': 'ssh',
 
232
                                                           'actions':
 
233
                                                           ['restart']}])
 
234
 
 
235
    def pre_write(self):
 
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,
 
243
                                     maxdepth=0)
 
244
 
 
245
    def post_write(self):
 
246
        # NOTE: don't recurse
 
247
        utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755,
 
248
                                 maxdepth=0)
 
249
 
 
250
 
 
251
class SSHConfigFileContentAudit(FileContentAudit):
 
252
    def __init__(self):
 
253
        self.path = '/etc/ssh/ssh_config'
 
254
        super(SSHConfigFileContentAudit, self).__init__(self.path, {})
 
255
 
 
256
    def is_compliant(self, *args, **kwargs):
 
257
        self.pass_cases = []
 
258
        self.fail_cases = []
 
259
        settings = utils.get_settings('ssh')
 
260
 
 
261
        if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
 
262
            if not settings['server']['weak_hmac']:
 
263
                self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
 
264
            else:
 
265
                self.pass_cases.append(r'^MACs.+,hmac-sha1$')
 
266
 
 
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
 
272
            else:
 
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
 
275
 
 
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]?')
 
281
            else:
 
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]?')
 
287
        else:
 
288
            if not settings['client']['weak_hmac']:
 
289
                self.fail_cases.append(r'^MACs.+,hmac-sha1$')
 
290
            else:
 
291
                self.pass_cases.append(r'^MACs.+,hmac-sha1$')
 
292
 
 
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
 
298
            else:
 
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
 
303
 
 
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]?')
 
309
            else:
 
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]?')
 
314
 
 
315
        if settings['client']['roaming']:
 
316
            self.pass_cases.append(r'^UseRoaming yes$')
 
317
        else:
 
318
            self.fail_cases.append(r'^UseRoaming yes$')
 
319
 
 
320
        return super(SSHConfigFileContentAudit, self).is_compliant(*args,
 
321
                                                                   **kwargs)
 
322
 
 
323
 
 
324
class SSHDConfigFileContentAudit(FileContentAudit):
 
325
    def __init__(self):
 
326
        self.path = '/etc/ssh/sshd_config'
 
327
        super(SSHDConfigFileContentAudit, self).__init__(self.path, {})
 
328
 
 
329
    def is_compliant(self, *args, **kwargs):
 
330
        self.pass_cases = []
 
331
        self.fail_cases = []
 
332
        settings = utils.get_settings('ssh')
 
333
 
 
334
        if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty':
 
335
            if not settings['server']['weak_hmac']:
 
336
                self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
 
337
            else:
 
338
                self.pass_cases.append(r'^MACs.+,hmac-sha1$')
 
339
 
 
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
 
345
            else:
 
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
 
348
 
 
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]?')
 
354
            else:
 
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]?')
 
360
        else:
 
361
            if not settings['server']['weak_hmac']:
 
362
                self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
 
363
            else:
 
364
                self.pass_cases.append(r'^MACs.+,hmac-sha1$')
 
365
 
 
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
 
371
            else:
 
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
 
376
 
 
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]?')
 
382
            else:
 
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]?')
 
387
 
 
388
        if settings['server']['sftp_enable']:
 
389
            self.pass_cases.append(r'^Subsystem\ssftp')
 
390
        else:
 
391
            self.fail_cases.append(r'^Subsystem\ssftp')
 
392
 
 
393
        return super(SSHDConfigFileContentAudit, self).is_compliant(*args,
 
394
                                                                    **kwargs)