Merge lp:~junaidali/charms/trusty/neutron-api-plumgrid/latest-ini into lp:~plumgrid-team/charms/trusty/neutron-api-plumgrid/trunk
- Trusty Tahr (14.04)
- latest-ini
- Merge into trunk
Proposed by
Junaid Ali
Status: | Merged |
---|---|
Merged at revision: | 20 |
Proposed branch: | lp:~junaidali/charms/trusty/neutron-api-plumgrid/latest-ini |
Merge into: | lp:~plumgrid-team/charms/trusty/neutron-api-plumgrid/trunk |
Diff against target: |
6412 lines (+124/-5807) 56 files modified
charm-helpers-sync.yaml (+6/-1) config.yaml (+14/-15) hooks/charmhelpers/contrib/ansible/__init__.py (+0/-254) hooks/charmhelpers/contrib/benchmark/__init__.py (+0/-126) hooks/charmhelpers/contrib/charmhelpers/__init__.py (+0/-208) hooks/charmhelpers/contrib/charmsupport/__init__.py (+0/-15) hooks/charmhelpers/contrib/charmsupport/nrpe.py (+0/-398) hooks/charmhelpers/contrib/charmsupport/volumes.py (+0/-175) hooks/charmhelpers/contrib/database/mysql.py (+0/-412) hooks/charmhelpers/contrib/hardening/__init__.py (+0/-15) hooks/charmhelpers/contrib/hardening/apache/__init__.py (+0/-19) hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py (+0/-31) hooks/charmhelpers/contrib/hardening/apache/checks/config.py (+0/-100) hooks/charmhelpers/contrib/hardening/audits/__init__.py (+0/-63) hooks/charmhelpers/contrib/hardening/audits/apache.py (+0/-100) hooks/charmhelpers/contrib/hardening/audits/apt.py (+0/-105) hooks/charmhelpers/contrib/hardening/audits/file.py (+0/-552) hooks/charmhelpers/contrib/hardening/harden.py (+0/-84) hooks/charmhelpers/contrib/hardening/host/__init__.py (+0/-19) hooks/charmhelpers/contrib/hardening/host/checks/__init__.py (+0/-50) hooks/charmhelpers/contrib/hardening/host/checks/apt.py (+0/-39) hooks/charmhelpers/contrib/hardening/host/checks/limits.py (+0/-55) hooks/charmhelpers/contrib/hardening/host/checks/login.py (+0/-67) hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py (+0/-52) hooks/charmhelpers/contrib/hardening/host/checks/pam.py (+0/-134) hooks/charmhelpers/contrib/hardening/host/checks/profile.py (+0/-45) hooks/charmhelpers/contrib/hardening/host/checks/securetty.py (+0/-39) hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py (+0/-131) hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py (+0/-211) hooks/charmhelpers/contrib/hardening/mysql/__init__.py (+0/-19) hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py (+0/-31) hooks/charmhelpers/contrib/hardening/mysql/checks/config.py (+0/-89) hooks/charmhelpers/contrib/hardening/ssh/__init__.py (+0/-19) hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py (+0/-31) hooks/charmhelpers/contrib/hardening/ssh/checks/config.py (+0/-394) hooks/charmhelpers/contrib/hardening/templating.py (+0/-71) hooks/charmhelpers/contrib/hardening/utils.py (+0/-157) hooks/charmhelpers/contrib/mellanox/infiniband.py (+0/-151) hooks/charmhelpers/contrib/peerstorage/__init__.py (+0/-269) hooks/charmhelpers/contrib/saltstack/__init__.py (+0/-118) hooks/charmhelpers/contrib/ssl/__init__.py (+0/-94) hooks/charmhelpers/contrib/ssl/service.py (+0/-279) hooks/charmhelpers/contrib/templating/__init__.py (+0/-15) hooks/charmhelpers/contrib/templating/contexts.py (+0/-139) hooks/charmhelpers/contrib/templating/jinja.py (+0/-40) hooks/charmhelpers/contrib/templating/pyformat.py (+0/-29) hooks/charmhelpers/contrib/unison/__init__.py (+0/-313) hooks/neutron_plumgrid_context.py (+37/-11) hooks/neutron_plumgrid_hooks.py (+7/-0) hooks/neutron_plumgrid_utils.py (+26/-3) metadata.yaml (+2/-0) templates/kilo/pgrc (+8/-0) templates/kilo/plumgrid.ini (+6/-0) templates/kilo/plumlib.ini (+4/-1) unit_tests/test_neutron_plumgrid_plugin_context.py (+11/-18) unit_tests/test_neutron_plumgrid_plugin_utils.py (+3/-1) |
To merge this branch: | bzr merge lp:~junaidali/charms/trusty/neutron-api-plumgrid/latest-ini |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Bilal Baqar | Pending | ||
Review via email: mp+293151@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 21. By Junaid Ali
-
l2VTEP and ECMP support plus unit_test fix
- 22. By Junaid Ali
-
Added pgrc file (ECMP support)
- 23. By Junaid Ali
-
changing legacy-mode's default value to True
- 24. By Junaid Ali
-
added relation with director to extract pg vip etc.
- 25. By Junaid Ali
-
added symlink
- 26. By Junaid Ali
-
fixing unit_test
- 27. By Junaid Ali
-
Updating functions names and code comments
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'charm-helpers-sync.yaml' |
2 | --- charm-helpers-sync.yaml 2015-07-29 18:35:16 +0000 |
3 | +++ charm-helpers-sync.yaml 2016-05-04 14:04:48 +0000 |
4 | @@ -3,5 +3,10 @@ |
5 | include: |
6 | - core |
7 | - fetch |
8 | - - contrib |
9 | + - contrib.amulet |
10 | + - contrib.hahelpers |
11 | + - contrib.network |
12 | + - contrib.openstack |
13 | + - contrib.python |
14 | + - contrib.storage |
15 | - payload |
16 | |
17 | === modified file 'config.yaml' |
18 | --- config.yaml 2016-04-21 12:26:10 +0000 |
19 | +++ config.yaml 2016-05-04 14:04:48 +0000 |
20 | @@ -1,7 +1,7 @@ |
21 | options: |
22 | enable-metadata: |
23 | type: boolean |
24 | - default: False |
25 | + default: True |
26 | description: "Set as True to enable metadata support" |
27 | install_sources: |
28 | default: 'ppa:plumgrid-team/stable' |
29 | @@ -21,22 +21,21 @@ |
30 | type: string |
31 | description: | |
32 | Provide the version of networking-plumgrid package that needs to be installed |
33 | - plumgrid-username: |
34 | - default: plumgrid |
35 | - type: string |
36 | - description: Username to access PLUMgrid Director |
37 | - plumgrid-password: |
38 | - default: plumgrid |
39 | - type: string |
40 | - description: Password to access PLUMgrid Director |
41 | - plumgrid-virtual-ip: |
42 | - default: 192.168.100.250 |
43 | - type: string |
44 | - description: IP address of PLUMgrid Director |
45 | - # end of PLUMgrid configuration |
46 | + hardware-vendor-name: |
47 | + type: string |
48 | + default: vendor_name |
49 | + description: Name of the supported hardware vendor |
50 | + switch-username: |
51 | + type: string |
52 | + default: plumgrid |
53 | + description: Username of the L2 gateway |
54 | + switch-password: |
55 | + type: string |
56 | + default: plumgrid |
57 | + description: Password of the L2 gateway |
58 | manage-neutron-plugin-legacy-mode: |
59 | type: boolean |
60 | - default: false |
61 | + default: True |
62 | description: | |
63 | If True neutron-api charm will install neutron packages for the plugin |
64 | configured. Also needs to be set in neutron-api charm |
65 | |
66 | === removed directory 'hooks/charmhelpers/contrib/ansible' |
67 | === removed file 'hooks/charmhelpers/contrib/ansible/__init__.py' |
68 | --- hooks/charmhelpers/contrib/ansible/__init__.py 2015-07-29 18:35:16 +0000 |
69 | +++ hooks/charmhelpers/contrib/ansible/__init__.py 1970-01-01 00:00:00 +0000 |
70 | @@ -1,254 +0,0 @@ |
71 | -# Copyright 2014-2015 Canonical Limited. |
72 | -# |
73 | -# This file is part of charm-helpers. |
74 | -# |
75 | -# charm-helpers is free software: you can redistribute it and/or modify |
76 | -# it under the terms of the GNU Lesser General Public License version 3 as |
77 | -# published by the Free Software Foundation. |
78 | -# |
79 | -# charm-helpers is distributed in the hope that it will be useful, |
80 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
81 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
82 | -# GNU Lesser General Public License for more details. |
83 | -# |
84 | -# You should have received a copy of the GNU Lesser General Public License |
85 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
86 | - |
87 | -# Copyright 2013 Canonical Ltd. |
88 | -# |
89 | -# Authors: |
90 | -# Charm Helpers Developers <juju@lists.ubuntu.com> |
91 | -"""Charm Helpers ansible - declare the state of your machines. |
92 | - |
93 | -This helper enables you to declare your machine state, rather than |
94 | -program it procedurally (and have to test each change to your procedures). |
95 | -Your install hook can be as simple as:: |
96 | - |
97 | - {{{ |
98 | - import charmhelpers.contrib.ansible |
99 | - |
100 | - |
101 | - def install(): |
102 | - charmhelpers.contrib.ansible.install_ansible_support() |
103 | - charmhelpers.contrib.ansible.apply_playbook('playbooks/install.yaml') |
104 | - }}} |
105 | - |
106 | -and won't need to change (nor will its tests) when you change the machine |
107 | -state. |
108 | - |
109 | -All of your juju config and relation-data are available as template |
110 | -variables within your playbooks and templates. An install playbook looks |
111 | -something like:: |
112 | - |
113 | - {{{ |
114 | - --- |
115 | - - hosts: localhost |
116 | - user: root |
117 | - |
118 | - tasks: |
119 | - - name: Add private repositories. |
120 | - template: |
121 | - src: ../templates/private-repositories.list.jinja2 |
122 | - dest: /etc/apt/sources.list.d/private.list |
123 | - |
124 | - - name: Update the cache. |
125 | - apt: update_cache=yes |
126 | - |
127 | - - name: Install dependencies. |
128 | - apt: pkg={{ item }} |
129 | - with_items: |
130 | - - python-mimeparse |
131 | - - python-webob |
132 | - - sunburnt |
133 | - |
134 | - - name: Setup groups. |
135 | - group: name={{ item.name }} gid={{ item.gid }} |
136 | - with_items: |
137 | - - { name: 'deploy_user', gid: 1800 } |
138 | - - { name: 'service_user', gid: 1500 } |
139 | - |
140 | - ... |
141 | - }}} |
142 | - |
143 | -Read more online about `playbooks`_ and standard ansible `modules`_. |
144 | - |
145 | -.. _playbooks: http://www.ansibleworks.com/docs/playbooks.html |
146 | -.. _modules: http://www.ansibleworks.com/docs/modules.html |
147 | - |
148 | -A further feature os the ansible hooks is to provide a light weight "action" |
149 | -scripting tool. This is a decorator that you apply to a function, and that |
150 | -function can now receive cli args, and can pass extra args to the playbook. |
151 | - |
152 | -e.g. |
153 | - |
154 | - |
155 | -@hooks.action() |
156 | -def some_action(amount, force="False"): |
157 | - "Usage: some-action AMOUNT [force=True]" # <-- shown on error |
158 | - # process the arguments |
159 | - # do some calls |
160 | - # return extra-vars to be passed to ansible-playbook |
161 | - return { |
162 | - 'amount': int(amount), |
163 | - 'type': force, |
164 | - } |
165 | - |
166 | -You can now create a symlink to hooks.py that can be invoked like a hook, but |
167 | -with cli params: |
168 | - |
169 | -# link actions/some-action to hooks/hooks.py |
170 | - |
171 | -actions/some-action amount=10 force=true |
172 | - |
173 | -""" |
174 | -import os |
175 | -import stat |
176 | -import subprocess |
177 | -import functools |
178 | - |
179 | -import charmhelpers.contrib.templating.contexts |
180 | -import charmhelpers.core.host |
181 | -import charmhelpers.core.hookenv |
182 | -import charmhelpers.fetch |
183 | - |
184 | - |
185 | -charm_dir = os.environ.get('CHARM_DIR', '') |
186 | -ansible_hosts_path = '/etc/ansible/hosts' |
187 | -# Ansible will automatically include any vars in the following |
188 | -# file in its inventory when run locally. |
189 | -ansible_vars_path = '/etc/ansible/host_vars/localhost' |
190 | - |
191 | - |
192 | -def install_ansible_support(from_ppa=True, ppa_location='ppa:rquillo/ansible'): |
193 | - """Installs the ansible package. |
194 | - |
195 | - By default it is installed from the `PPA`_ linked from |
196 | - the ansible `website`_ or from a ppa specified by a charm config.. |
197 | - |
198 | - .. _PPA: https://launchpad.net/~rquillo/+archive/ansible |
199 | - .. _website: http://docs.ansible.com/intro_installation.html#latest-releases-via-apt-ubuntu |
200 | - |
201 | - If from_ppa is empty, you must ensure that the package is available |
202 | - from a configured repository. |
203 | - """ |
204 | - if from_ppa: |
205 | - charmhelpers.fetch.add_source(ppa_location) |
206 | - charmhelpers.fetch.apt_update(fatal=True) |
207 | - charmhelpers.fetch.apt_install('ansible') |
208 | - with open(ansible_hosts_path, 'w+') as hosts_file: |
209 | - hosts_file.write('localhost ansible_connection=local') |
210 | - |
211 | - |
212 | -def apply_playbook(playbook, tags=None, extra_vars=None): |
213 | - tags = tags or [] |
214 | - tags = ",".join(tags) |
215 | - charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
216 | - ansible_vars_path, namespace_separator='__', |
217 | - allow_hyphens_in_keys=False, mode=(stat.S_IRUSR | stat.S_IWUSR)) |
218 | - |
219 | - # we want ansible's log output to be unbuffered |
220 | - env = os.environ.copy() |
221 | - env['PYTHONUNBUFFERED'] = "1" |
222 | - call = [ |
223 | - 'ansible-playbook', |
224 | - '-c', |
225 | - 'local', |
226 | - playbook, |
227 | - ] |
228 | - if tags: |
229 | - call.extend(['--tags', '{}'.format(tags)]) |
230 | - if extra_vars: |
231 | - extra = ["%s=%s" % (k, v) for k, v in extra_vars.items()] |
232 | - call.extend(['--extra-vars', " ".join(extra)]) |
233 | - subprocess.check_call(call, env=env) |
234 | - |
235 | - |
236 | -class AnsibleHooks(charmhelpers.core.hookenv.Hooks): |
237 | - """Run a playbook with the hook-name as the tag. |
238 | - |
239 | - This helper builds on the standard hookenv.Hooks helper, |
240 | - but additionally runs the playbook with the hook-name specified |
241 | - using --tags (ie. running all the tasks tagged with the hook-name). |
242 | - |
243 | - Example:: |
244 | - |
245 | - hooks = AnsibleHooks(playbook_path='playbooks/my_machine_state.yaml') |
246 | - |
247 | - # All the tasks within my_machine_state.yaml tagged with 'install' |
248 | - # will be run automatically after do_custom_work() |
249 | - @hooks.hook() |
250 | - def install(): |
251 | - do_custom_work() |
252 | - |
253 | - # For most of your hooks, you won't need to do anything other |
254 | - # than run the tagged tasks for the hook: |
255 | - @hooks.hook('config-changed', 'start', 'stop') |
256 | - def just_use_playbook(): |
257 | - pass |
258 | - |
259 | - # As a convenience, you can avoid the above noop function by specifying |
260 | - # the hooks which are handled by ansible-only and they'll be registered |
261 | - # for you: |
262 | - # hooks = AnsibleHooks( |
263 | - # 'playbooks/my_machine_state.yaml', |
264 | - # default_hooks=['config-changed', 'start', 'stop']) |
265 | - |
266 | - if __name__ == "__main__": |
267 | - # execute a hook based on the name the program is called by |
268 | - hooks.execute(sys.argv) |
269 | - |
270 | - """ |
271 | - |
272 | - def __init__(self, playbook_path, default_hooks=None): |
273 | - """Register any hooks handled by ansible.""" |
274 | - super(AnsibleHooks, self).__init__() |
275 | - |
276 | - self._actions = {} |
277 | - self.playbook_path = playbook_path |
278 | - |
279 | - default_hooks = default_hooks or [] |
280 | - |
281 | - def noop(*args, **kwargs): |
282 | - pass |
283 | - |
284 | - for hook in default_hooks: |
285 | - self.register(hook, noop) |
286 | - |
287 | - def register_action(self, name, function): |
288 | - """Register a hook""" |
289 | - self._actions[name] = function |
290 | - |
291 | - def execute(self, args): |
292 | - """Execute the hook followed by the playbook using the hook as tag.""" |
293 | - hook_name = os.path.basename(args[0]) |
294 | - extra_vars = None |
295 | - if hook_name in self._actions: |
296 | - extra_vars = self._actions[hook_name](args[1:]) |
297 | - else: |
298 | - super(AnsibleHooks, self).execute(args) |
299 | - |
300 | - charmhelpers.contrib.ansible.apply_playbook( |
301 | - self.playbook_path, tags=[hook_name], extra_vars=extra_vars) |
302 | - |
303 | - def action(self, *action_names): |
304 | - """Decorator, registering them as actions""" |
305 | - def action_wrapper(decorated): |
306 | - |
307 | - @functools.wraps(decorated) |
308 | - def wrapper(argv): |
309 | - kwargs = dict(arg.split('=') for arg in argv) |
310 | - try: |
311 | - return decorated(**kwargs) |
312 | - except TypeError as e: |
313 | - if decorated.__doc__: |
314 | - e.args += (decorated.__doc__,) |
315 | - raise |
316 | - |
317 | - self.register_action(decorated.__name__, wrapper) |
318 | - if '_' in decorated.__name__: |
319 | - self.register_action( |
320 | - decorated.__name__.replace('_', '-'), wrapper) |
321 | - |
322 | - return wrapper |
323 | - |
324 | - return action_wrapper |
325 | |
326 | === removed directory 'hooks/charmhelpers/contrib/benchmark' |
327 | === removed file 'hooks/charmhelpers/contrib/benchmark/__init__.py' |
328 | --- hooks/charmhelpers/contrib/benchmark/__init__.py 2015-07-29 18:35:16 +0000 |
329 | +++ hooks/charmhelpers/contrib/benchmark/__init__.py 1970-01-01 00:00:00 +0000 |
330 | @@ -1,126 +0,0 @@ |
331 | -# Copyright 2014-2015 Canonical Limited. |
332 | -# |
333 | -# This file is part of charm-helpers. |
334 | -# |
335 | -# charm-helpers is free software: you can redistribute it and/or modify |
336 | -# it under the terms of the GNU Lesser General Public License version 3 as |
337 | -# published by the Free Software Foundation. |
338 | -# |
339 | -# charm-helpers is distributed in the hope that it will be useful, |
340 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
341 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
342 | -# GNU Lesser General Public License for more details. |
343 | -# |
344 | -# You should have received a copy of the GNU Lesser General Public License |
345 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
346 | - |
347 | -import subprocess |
348 | -import time |
349 | -import os |
350 | -from distutils.spawn import find_executable |
351 | - |
352 | -from charmhelpers.core.hookenv import ( |
353 | - in_relation_hook, |
354 | - relation_ids, |
355 | - relation_set, |
356 | - relation_get, |
357 | -) |
358 | - |
359 | - |
360 | -def action_set(key, val): |
361 | - if find_executable('action-set'): |
362 | - action_cmd = ['action-set'] |
363 | - |
364 | - if isinstance(val, dict): |
365 | - for k, v in iter(val.items()): |
366 | - action_set('%s.%s' % (key, k), v) |
367 | - return True |
368 | - |
369 | - action_cmd.append('%s=%s' % (key, val)) |
370 | - subprocess.check_call(action_cmd) |
371 | - return True |
372 | - return False |
373 | - |
374 | - |
375 | -class Benchmark(): |
376 | - """ |
377 | - Helper class for the `benchmark` interface. |
378 | - |
379 | - :param list actions: Define the actions that are also benchmarks |
380 | - |
381 | - From inside the benchmark-relation-changed hook, you would |
382 | - Benchmark(['memory', 'cpu', 'disk', 'smoke', 'custom']) |
383 | - |
384 | - Examples: |
385 | - |
386 | - siege = Benchmark(['siege']) |
387 | - siege.start() |
388 | - [... run siege ...] |
389 | - # The higher the score, the better the benchmark |
390 | - siege.set_composite_score(16.70, 'trans/sec', 'desc') |
391 | - siege.finish() |
392 | - |
393 | - |
394 | - """ |
395 | - |
396 | - BENCHMARK_CONF = '/etc/benchmark.conf' # Replaced in testing |
397 | - |
398 | - required_keys = [ |
399 | - 'hostname', |
400 | - 'port', |
401 | - 'graphite_port', |
402 | - 'graphite_endpoint', |
403 | - 'api_port' |
404 | - ] |
405 | - |
406 | - def __init__(self, benchmarks=None): |
407 | - if in_relation_hook(): |
408 | - if benchmarks is not None: |
409 | - for rid in sorted(relation_ids('benchmark')): |
410 | - relation_set(relation_id=rid, relation_settings={ |
411 | - 'benchmarks': ",".join(benchmarks) |
412 | - }) |
413 | - |
414 | - # Check the relation data |
415 | - config = {} |
416 | - for key in self.required_keys: |
417 | - val = relation_get(key) |
418 | - if val is not None: |
419 | - config[key] = val |
420 | - else: |
421 | - # We don't have all of the required keys |
422 | - config = {} |
423 | - break |
424 | - |
425 | - if len(config): |
426 | - with open(self.BENCHMARK_CONF, 'w') as f: |
427 | - for key, val in iter(config.items()): |
428 | - f.write("%s=%s\n" % (key, val)) |
429 | - |
430 | - @staticmethod |
431 | - def start(): |
432 | - action_set('meta.start', time.strftime('%Y-%m-%dT%H:%M:%SZ')) |
433 | - |
434 | - """ |
435 | - If the collectd charm is also installed, tell it to send a snapshot |
436 | - of the current profile data. |
437 | - """ |
438 | - COLLECT_PROFILE_DATA = '/usr/local/bin/collect-profile-data' |
439 | - if os.path.exists(COLLECT_PROFILE_DATA): |
440 | - subprocess.check_output([COLLECT_PROFILE_DATA]) |
441 | - |
442 | - @staticmethod |
443 | - def finish(): |
444 | - action_set('meta.stop', time.strftime('%Y-%m-%dT%H:%M:%SZ')) |
445 | - |
446 | - @staticmethod |
447 | - def set_composite_score(value, units, direction='asc'): |
448 | - """ |
449 | - Set the composite score for a benchmark run. This is a single number |
450 | - representative of the benchmark results. This could be the most |
451 | - important metric, or an amalgamation of metric scores. |
452 | - """ |
453 | - return action_set( |
454 | - "meta.composite", |
455 | - {'value': value, 'units': units, 'direction': direction} |
456 | - ) |
457 | |
458 | === removed directory 'hooks/charmhelpers/contrib/charmhelpers' |
459 | === removed file 'hooks/charmhelpers/contrib/charmhelpers/__init__.py' |
460 | --- hooks/charmhelpers/contrib/charmhelpers/__init__.py 2015-07-29 18:35:16 +0000 |
461 | +++ hooks/charmhelpers/contrib/charmhelpers/__init__.py 1970-01-01 00:00:00 +0000 |
462 | @@ -1,208 +0,0 @@ |
463 | -# Copyright 2014-2015 Canonical Limited. |
464 | -# |
465 | -# This file is part of charm-helpers. |
466 | -# |
467 | -# charm-helpers is free software: you can redistribute it and/or modify |
468 | -# it under the terms of the GNU Lesser General Public License version 3 as |
469 | -# published by the Free Software Foundation. |
470 | -# |
471 | -# charm-helpers is distributed in the hope that it will be useful, |
472 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
473 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
474 | -# GNU Lesser General Public License for more details. |
475 | -# |
476 | -# You should have received a copy of the GNU Lesser General Public License |
477 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
478 | - |
479 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
480 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
481 | - |
482 | -import warnings |
483 | -warnings.warn("contrib.charmhelpers is deprecated", DeprecationWarning) # noqa |
484 | - |
485 | -import operator |
486 | -import tempfile |
487 | -import time |
488 | -import yaml |
489 | -import subprocess |
490 | - |
491 | -import six |
492 | -if six.PY3: |
493 | - from urllib.request import urlopen |
494 | - from urllib.error import (HTTPError, URLError) |
495 | -else: |
496 | - from urllib2 import (urlopen, HTTPError, URLError) |
497 | - |
498 | -"""Helper functions for writing Juju charms in Python.""" |
499 | - |
500 | -__metaclass__ = type |
501 | -__all__ = [ |
502 | - # 'get_config', # core.hookenv.config() |
503 | - # 'log', # core.hookenv.log() |
504 | - # 'log_entry', # core.hookenv.log() |
505 | - # 'log_exit', # core.hookenv.log() |
506 | - # 'relation_get', # core.hookenv.relation_get() |
507 | - # 'relation_set', # core.hookenv.relation_set() |
508 | - # 'relation_ids', # core.hookenv.relation_ids() |
509 | - # 'relation_list', # core.hookenv.relation_units() |
510 | - # 'config_get', # core.hookenv.config() |
511 | - # 'unit_get', # core.hookenv.unit_get() |
512 | - # 'open_port', # core.hookenv.open_port() |
513 | - # 'close_port', # core.hookenv.close_port() |
514 | - # 'service_control', # core.host.service() |
515 | - 'unit_info', # client-side, NOT IMPLEMENTED |
516 | - 'wait_for_machine', # client-side, NOT IMPLEMENTED |
517 | - 'wait_for_page_contents', # client-side, NOT IMPLEMENTED |
518 | - 'wait_for_relation', # client-side, NOT IMPLEMENTED |
519 | - 'wait_for_unit', # client-side, NOT IMPLEMENTED |
520 | -] |
521 | - |
522 | - |
523 | -SLEEP_AMOUNT = 0.1 |
524 | - |
525 | - |
526 | -# We create a juju_status Command here because it makes testing much, |
527 | -# much easier. |
528 | -def juju_status(): |
529 | - subprocess.check_call(['juju', 'status']) |
530 | - |
531 | -# re-implemented as charmhelpers.fetch.configure_sources() |
532 | -# def configure_source(update=False): |
533 | -# source = config_get('source') |
534 | -# if ((source.startswith('ppa:') or |
535 | -# source.startswith('cloud:') or |
536 | -# source.startswith('http:'))): |
537 | -# run('add-apt-repository', source) |
538 | -# if source.startswith("http:"): |
539 | -# run('apt-key', 'import', config_get('key')) |
540 | -# if update: |
541 | -# run('apt-get', 'update') |
542 | - |
543 | - |
544 | -# DEPRECATED: client-side only |
545 | -def make_charm_config_file(charm_config): |
546 | - charm_config_file = tempfile.NamedTemporaryFile(mode='w+') |
547 | - charm_config_file.write(yaml.dump(charm_config)) |
548 | - charm_config_file.flush() |
549 | - # The NamedTemporaryFile instance is returned instead of just the name |
550 | - # because we want to take advantage of garbage collection-triggered |
551 | - # deletion of the temp file when it goes out of scope in the caller. |
552 | - return charm_config_file |
553 | - |
554 | - |
555 | -# DEPRECATED: client-side only |
556 | -def unit_info(service_name, item_name, data=None, unit=None): |
557 | - if data is None: |
558 | - data = yaml.safe_load(juju_status()) |
559 | - service = data['services'].get(service_name) |
560 | - if service is None: |
561 | - # XXX 2012-02-08 gmb: |
562 | - # This allows us to cope with the race condition that we |
563 | - # have between deploying a service and having it come up in |
564 | - # `juju status`. We could probably do with cleaning it up so |
565 | - # that it fails a bit more noisily after a while. |
566 | - return '' |
567 | - units = service['units'] |
568 | - if unit is not None: |
569 | - item = units[unit][item_name] |
570 | - else: |
571 | - # It might seem odd to sort the units here, but we do it to |
572 | - # ensure that when no unit is specified, the first unit for the |
573 | - # service (or at least the one with the lowest number) is the |
574 | - # one whose data gets returned. |
575 | - sorted_unit_names = sorted(units.keys()) |
576 | - item = units[sorted_unit_names[0]][item_name] |
577 | - return item |
578 | - |
579 | - |
580 | -# DEPRECATED: client-side only |
581 | -def get_machine_data(): |
582 | - return yaml.safe_load(juju_status())['machines'] |
583 | - |
584 | - |
585 | -# DEPRECATED: client-side only |
586 | -def wait_for_machine(num_machines=1, timeout=300): |
587 | - """Wait `timeout` seconds for `num_machines` machines to come up. |
588 | - |
589 | - This wait_for... function can be called by other wait_for functions |
590 | - whose timeouts might be too short in situations where only a bare |
591 | - Juju setup has been bootstrapped. |
592 | - |
593 | - :return: A tuple of (num_machines, time_taken). This is used for |
594 | - testing. |
595 | - """ |
596 | - # You may think this is a hack, and you'd be right. The easiest way |
597 | - # to tell what environment we're working in (LXC vs EC2) is to check |
598 | - # the dns-name of the first machine. If it's localhost we're in LXC |
599 | - # and we can just return here. |
600 | - if get_machine_data()[0]['dns-name'] == 'localhost': |
601 | - return 1, 0 |
602 | - start_time = time.time() |
603 | - while True: |
604 | - # Drop the first machine, since it's the Zookeeper and that's |
605 | - # not a machine that we need to wait for. This will only work |
606 | - # for EC2 environments, which is why we return early above if |
607 | - # we're in LXC. |
608 | - machine_data = get_machine_data() |
609 | - non_zookeeper_machines = [ |
610 | - machine_data[key] for key in list(machine_data.keys())[1:]] |
611 | - if len(non_zookeeper_machines) >= num_machines: |
612 | - all_machines_running = True |
613 | - for machine in non_zookeeper_machines: |
614 | - if machine.get('instance-state') != 'running': |
615 | - all_machines_running = False |
616 | - break |
617 | - if all_machines_running: |
618 | - break |
619 | - if time.time() - start_time >= timeout: |
620 | - raise RuntimeError('timeout waiting for service to start') |
621 | - time.sleep(SLEEP_AMOUNT) |
622 | - return num_machines, time.time() - start_time |
623 | - |
624 | - |
625 | -# DEPRECATED: client-side only |
626 | -def wait_for_unit(service_name, timeout=480): |
627 | - """Wait `timeout` seconds for a given service name to come up.""" |
628 | - wait_for_machine(num_machines=1) |
629 | - start_time = time.time() |
630 | - while True: |
631 | - state = unit_info(service_name, 'agent-state') |
632 | - if 'error' in state or state == 'started': |
633 | - break |
634 | - if time.time() - start_time >= timeout: |
635 | - raise RuntimeError('timeout waiting for service to start') |
636 | - time.sleep(SLEEP_AMOUNT) |
637 | - if state != 'started': |
638 | - raise RuntimeError('unit did not start, agent-state: ' + state) |
639 | - |
640 | - |
641 | -# DEPRECATED: client-side only |
642 | -def wait_for_relation(service_name, relation_name, timeout=120): |
643 | - """Wait `timeout` seconds for a given relation to come up.""" |
644 | - start_time = time.time() |
645 | - while True: |
646 | - relation = unit_info(service_name, 'relations').get(relation_name) |
647 | - if relation is not None and relation['state'] == 'up': |
648 | - break |
649 | - if time.time() - start_time >= timeout: |
650 | - raise RuntimeError('timeout waiting for relation to be up') |
651 | - time.sleep(SLEEP_AMOUNT) |
652 | - |
653 | - |
654 | -# DEPRECATED: client-side only |
655 | -def wait_for_page_contents(url, contents, timeout=120, validate=None): |
656 | - if validate is None: |
657 | - validate = operator.contains |
658 | - start_time = time.time() |
659 | - while True: |
660 | - try: |
661 | - stream = urlopen(url) |
662 | - except (HTTPError, URLError): |
663 | - pass |
664 | - else: |
665 | - page = stream.read() |
666 | - if validate(page, contents): |
667 | - return page |
668 | - if time.time() - start_time >= timeout: |
669 | - raise RuntimeError('timeout waiting for contents of ' + url) |
670 | - time.sleep(SLEEP_AMOUNT) |
671 | |
672 | === removed directory 'hooks/charmhelpers/contrib/charmsupport' |
673 | === removed file 'hooks/charmhelpers/contrib/charmsupport/__init__.py' |
674 | --- hooks/charmhelpers/contrib/charmsupport/__init__.py 2015-07-29 18:35:16 +0000 |
675 | +++ hooks/charmhelpers/contrib/charmsupport/__init__.py 1970-01-01 00:00:00 +0000 |
676 | @@ -1,15 +0,0 @@ |
677 | -# Copyright 2014-2015 Canonical Limited. |
678 | -# |
679 | -# This file is part of charm-helpers. |
680 | -# |
681 | -# charm-helpers is free software: you can redistribute it and/or modify |
682 | -# it under the terms of the GNU Lesser General Public License version 3 as |
683 | -# published by the Free Software Foundation. |
684 | -# |
685 | -# charm-helpers is distributed in the hope that it will be useful, |
686 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
687 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
688 | -# GNU Lesser General Public License for more details. |
689 | -# |
690 | -# You should have received a copy of the GNU Lesser General Public License |
691 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
692 | |
693 | === removed file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py' |
694 | --- hooks/charmhelpers/contrib/charmsupport/nrpe.py 2016-02-27 19:51:32 +0000 |
695 | +++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000 |
696 | @@ -1,398 +0,0 @@ |
697 | -# Copyright 2014-2015 Canonical Limited. |
698 | -# |
699 | -# This file is part of charm-helpers. |
700 | -# |
701 | -# charm-helpers is free software: you can redistribute it and/or modify |
702 | -# it under the terms of the GNU Lesser General Public License version 3 as |
703 | -# published by the Free Software Foundation. |
704 | -# |
705 | -# charm-helpers is distributed in the hope that it will be useful, |
706 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
707 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
708 | -# GNU Lesser General Public License for more details. |
709 | -# |
710 | -# You should have received a copy of the GNU Lesser General Public License |
711 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
712 | - |
713 | -"""Compatibility with the nrpe-external-master charm""" |
714 | -# Copyright 2012 Canonical Ltd. |
715 | -# |
716 | -# Authors: |
717 | -# Matthew Wedgwood <matthew.wedgwood@canonical.com> |
718 | - |
719 | -import subprocess |
720 | -import pwd |
721 | -import grp |
722 | -import os |
723 | -import glob |
724 | -import shutil |
725 | -import re |
726 | -import shlex |
727 | -import yaml |
728 | - |
729 | -from charmhelpers.core.hookenv import ( |
730 | - config, |
731 | - local_unit, |
732 | - log, |
733 | - relation_ids, |
734 | - relation_set, |
735 | - relations_of_type, |
736 | -) |
737 | - |
738 | -from charmhelpers.core.host import service |
739 | - |
740 | -# This module adds compatibility with the nrpe-external-master and plain nrpe |
741 | -# subordinate charms. To use it in your charm: |
742 | -# |
743 | -# 1. Update metadata.yaml |
744 | -# |
745 | -# provides: |
746 | -# (...) |
747 | -# nrpe-external-master: |
748 | -# interface: nrpe-external-master |
749 | -# scope: container |
750 | -# |
751 | -# and/or |
752 | -# |
753 | -# provides: |
754 | -# (...) |
755 | -# local-monitors: |
756 | -# interface: local-monitors |
757 | -# scope: container |
758 | - |
759 | -# |
760 | -# 2. Add the following to config.yaml |
761 | -# |
762 | -# nagios_context: |
763 | -# default: "juju" |
764 | -# type: string |
765 | -# description: | |
766 | -# Used by the nrpe subordinate charms. |
767 | -# A string that will be prepended to instance name to set the host name |
768 | -# in nagios. So for instance the hostname would be something like: |
769 | -# juju-myservice-0 |
770 | -# If you're running multiple environments with the same services in them |
771 | -# this allows you to differentiate between them. |
772 | -# nagios_servicegroups: |
773 | -# default: "" |
774 | -# type: string |
775 | -# description: | |
776 | -# A comma-separated list of nagios servicegroups. |
777 | -# If left empty, the nagios_context will be used as the servicegroup |
778 | -# |
779 | -# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master |
780 | -# |
781 | -# 4. Update your hooks.py with something like this: |
782 | -# |
783 | -# from charmsupport.nrpe import NRPE |
784 | -# (...) |
785 | -# def update_nrpe_config(): |
786 | -# nrpe_compat = NRPE() |
787 | -# nrpe_compat.add_check( |
788 | -# shortname = "myservice", |
789 | -# description = "Check MyService", |
790 | -# check_cmd = "check_http -w 2 -c 10 http://localhost" |
791 | -# ) |
792 | -# nrpe_compat.add_check( |
793 | -# "myservice_other", |
794 | -# "Check for widget failures", |
795 | -# check_cmd = "/srv/myapp/scripts/widget_check" |
796 | -# ) |
797 | -# nrpe_compat.write() |
798 | -# |
799 | -# def config_changed(): |
800 | -# (...) |
801 | -# update_nrpe_config() |
802 | -# |
803 | -# def nrpe_external_master_relation_changed(): |
804 | -# update_nrpe_config() |
805 | -# |
806 | -# def local_monitors_relation_changed(): |
807 | -# update_nrpe_config() |
808 | -# |
809 | -# 5. ln -s hooks.py nrpe-external-master-relation-changed |
810 | -# ln -s hooks.py local-monitors-relation-changed |
811 | - |
812 | - |
813 | -class CheckException(Exception): |
814 | - pass |
815 | - |
816 | - |
817 | -class Check(object): |
818 | - shortname_re = '[A-Za-z0-9-_]+$' |
819 | - service_template = (""" |
820 | -#--------------------------------------------------- |
821 | -# This file is Juju managed |
822 | -#--------------------------------------------------- |
823 | -define service {{ |
824 | - use active-service |
825 | - host_name {nagios_hostname} |
826 | - service_description {nagios_hostname}[{shortname}] """ |
827 | - """{description} |
828 | - check_command check_nrpe!{command} |
829 | - servicegroups {nagios_servicegroup} |
830 | -}} |
831 | -""") |
832 | - |
833 | - def __init__(self, shortname, description, check_cmd): |
834 | - super(Check, self).__init__() |
835 | - # XXX: could be better to calculate this from the service name |
836 | - if not re.match(self.shortname_re, shortname): |
837 | - raise CheckException("shortname must match {}".format( |
838 | - Check.shortname_re)) |
839 | - self.shortname = shortname |
840 | - self.command = "check_{}".format(shortname) |
841 | - # Note: a set of invalid characters is defined by the |
842 | - # Nagios server config |
843 | - # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()= |
844 | - self.description = description |
845 | - self.check_cmd = self._locate_cmd(check_cmd) |
846 | - |
847 | - def _get_check_filename(self): |
848 | - return os.path.join(NRPE.nrpe_confdir, '{}.cfg'.format(self.command)) |
849 | - |
850 | - def _get_service_filename(self, hostname): |
851 | - return os.path.join(NRPE.nagios_exportdir, |
852 | - 'service__{}_{}.cfg'.format(hostname, self.command)) |
853 | - |
854 | - def _locate_cmd(self, check_cmd): |
855 | - search_path = ( |
856 | - '/usr/lib/nagios/plugins', |
857 | - '/usr/local/lib/nagios/plugins', |
858 | - ) |
859 | - parts = shlex.split(check_cmd) |
860 | - for path in search_path: |
861 | - if os.path.exists(os.path.join(path, parts[0])): |
862 | - command = os.path.join(path, parts[0]) |
863 | - if len(parts) > 1: |
864 | - command += " " + " ".join(parts[1:]) |
865 | - return command |
866 | - log('Check command not found: {}'.format(parts[0])) |
867 | - return '' |
868 | - |
869 | - def _remove_service_files(self): |
870 | - if not os.path.exists(NRPE.nagios_exportdir): |
871 | - return |
872 | - for f in os.listdir(NRPE.nagios_exportdir): |
873 | - if f.endswith('_{}.cfg'.format(self.command)): |
874 | - os.remove(os.path.join(NRPE.nagios_exportdir, f)) |
875 | - |
876 | - def remove(self, hostname): |
877 | - nrpe_check_file = self._get_check_filename() |
878 | - if os.path.exists(nrpe_check_file): |
879 | - os.remove(nrpe_check_file) |
880 | - self._remove_service_files() |
881 | - |
882 | - def write(self, nagios_context, hostname, nagios_servicegroups): |
883 | - nrpe_check_file = self._get_check_filename() |
884 | - with open(nrpe_check_file, 'w') as nrpe_check_config: |
885 | - nrpe_check_config.write("# check {}\n".format(self.shortname)) |
886 | - nrpe_check_config.write("command[{}]={}\n".format( |
887 | - self.command, self.check_cmd)) |
888 | - |
889 | - if not os.path.exists(NRPE.nagios_exportdir): |
890 | - log('Not writing service config as {} is not accessible'.format( |
891 | - NRPE.nagios_exportdir)) |
892 | - else: |
893 | - self.write_service_config(nagios_context, hostname, |
894 | - nagios_servicegroups) |
895 | - |
896 | - def write_service_config(self, nagios_context, hostname, |
897 | - nagios_servicegroups): |
898 | - self._remove_service_files() |
899 | - |
900 | - templ_vars = { |
901 | - 'nagios_hostname': hostname, |
902 | - 'nagios_servicegroup': nagios_servicegroups, |
903 | - 'description': self.description, |
904 | - 'shortname': self.shortname, |
905 | - 'command': self.command, |
906 | - } |
907 | - nrpe_service_text = Check.service_template.format(**templ_vars) |
908 | - nrpe_service_file = self._get_service_filename(hostname) |
909 | - with open(nrpe_service_file, 'w') as nrpe_service_config: |
910 | - nrpe_service_config.write(str(nrpe_service_text)) |
911 | - |
912 | - def run(self): |
913 | - subprocess.call(self.check_cmd) |
914 | - |
915 | - |
916 | -class NRPE(object): |
917 | - nagios_logdir = '/var/log/nagios' |
918 | - nagios_exportdir = '/var/lib/nagios/export' |
919 | - nrpe_confdir = '/etc/nagios/nrpe.d' |
920 | - |
921 | - def __init__(self, hostname=None): |
922 | - super(NRPE, self).__init__() |
923 | - self.config = config() |
924 | - self.nagios_context = self.config['nagios_context'] |
925 | - if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']: |
926 | - self.nagios_servicegroups = self.config['nagios_servicegroups'] |
927 | - else: |
928 | - self.nagios_servicegroups = self.nagios_context |
929 | - self.unit_name = local_unit().replace('/', '-') |
930 | - if hostname: |
931 | - self.hostname = hostname |
932 | - else: |
933 | - nagios_hostname = get_nagios_hostname() |
934 | - if nagios_hostname: |
935 | - self.hostname = nagios_hostname |
936 | - else: |
937 | - self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) |
938 | - self.checks = [] |
939 | - |
940 | - def add_check(self, *args, **kwargs): |
941 | - self.checks.append(Check(*args, **kwargs)) |
942 | - |
943 | - def remove_check(self, *args, **kwargs): |
944 | - if kwargs.get('shortname') is None: |
945 | - raise ValueError('shortname of check must be specified') |
946 | - |
947 | - # Use sensible defaults if they're not specified - these are not |
948 | - # actually used during removal, but they're required for constructing |
949 | - # the Check object; check_disk is chosen because it's part of the |
950 | - # nagios-plugins-basic package. |
951 | - if kwargs.get('check_cmd') is None: |
952 | - kwargs['check_cmd'] = 'check_disk' |
953 | - if kwargs.get('description') is None: |
954 | - kwargs['description'] = '' |
955 | - |
956 | - check = Check(*args, **kwargs) |
957 | - check.remove(self.hostname) |
958 | - |
959 | - def write(self): |
960 | - try: |
961 | - nagios_uid = pwd.getpwnam('nagios').pw_uid |
962 | - nagios_gid = grp.getgrnam('nagios').gr_gid |
963 | - except: |
964 | - log("Nagios user not set up, nrpe checks not updated") |
965 | - return |
966 | - |
967 | - if not os.path.exists(NRPE.nagios_logdir): |
968 | - os.mkdir(NRPE.nagios_logdir) |
969 | - os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid) |
970 | - |
971 | - nrpe_monitors = {} |
972 | - monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}} |
973 | - for nrpecheck in self.checks: |
974 | - nrpecheck.write(self.nagios_context, self.hostname, |
975 | - self.nagios_servicegroups) |
976 | - nrpe_monitors[nrpecheck.shortname] = { |
977 | - "command": nrpecheck.command, |
978 | - } |
979 | - |
980 | - service('restart', 'nagios-nrpe-server') |
981 | - |
982 | - monitor_ids = relation_ids("local-monitors") + \ |
983 | - relation_ids("nrpe-external-master") |
984 | - for rid in monitor_ids: |
985 | - relation_set(relation_id=rid, monitors=yaml.dump(monitors)) |
986 | - |
987 | - |
988 | -def get_nagios_hostcontext(relation_name='nrpe-external-master'): |
989 | - """ |
990 | - Query relation with nrpe subordinate, return the nagios_host_context |
991 | - |
992 | - :param str relation_name: Name of relation nrpe sub joined to |
993 | - """ |
994 | - for rel in relations_of_type(relation_name): |
995 | - if 'nagios_host_context' in rel: |
996 | - return rel['nagios_host_context'] |
997 | - |
998 | - |
999 | -def get_nagios_hostname(relation_name='nrpe-external-master'): |
1000 | - """ |
1001 | - Query relation with nrpe subordinate, return the nagios_hostname |
1002 | - |
1003 | - :param str relation_name: Name of relation nrpe sub joined to |
1004 | - """ |
1005 | - for rel in relations_of_type(relation_name): |
1006 | - if 'nagios_hostname' in rel: |
1007 | - return rel['nagios_hostname'] |
1008 | - |
1009 | - |
1010 | -def get_nagios_unit_name(relation_name='nrpe-external-master'): |
1011 | - """ |
1012 | - Return the nagios unit name prepended with host_context if needed |
1013 | - |
1014 | - :param str relation_name: Name of relation nrpe sub joined to |
1015 | - """ |
1016 | - host_context = get_nagios_hostcontext(relation_name) |
1017 | - if host_context: |
1018 | - unit = "%s:%s" % (host_context, local_unit()) |
1019 | - else: |
1020 | - unit = local_unit() |
1021 | - return unit |
1022 | - |
1023 | - |
1024 | -def add_init_service_checks(nrpe, services, unit_name): |
1025 | - """ |
1026 | - Add checks for each service in list |
1027 | - |
1028 | - :param NRPE nrpe: NRPE object to add check to |
1029 | - :param list services: List of services to check |
1030 | - :param str unit_name: Unit name to use in check description |
1031 | - """ |
1032 | - for svc in services: |
1033 | - upstart_init = '/etc/init/%s.conf' % svc |
1034 | - sysv_init = '/etc/init.d/%s' % svc |
1035 | - if os.path.exists(upstart_init): |
1036 | - # Don't add a check for these services from neutron-gateway |
1037 | - if svc not in ['ext-port', 'os-charm-phy-nic-mtu']: |
1038 | - nrpe.add_check( |
1039 | - shortname=svc, |
1040 | - description='process check {%s}' % unit_name, |
1041 | - check_cmd='check_upstart_job %s' % svc |
1042 | - ) |
1043 | - elif os.path.exists(sysv_init): |
1044 | - cronpath = '/etc/cron.d/nagios-service-check-%s' % svc |
1045 | - cron_file = ('*/5 * * * * root ' |
1046 | - '/usr/local/lib/nagios/plugins/check_exit_status.pl ' |
1047 | - '-s /etc/init.d/%s status > ' |
1048 | - '/var/lib/nagios/service-check-%s.txt\n' % (svc, |
1049 | - svc) |
1050 | - ) |
1051 | - f = open(cronpath, 'w') |
1052 | - f.write(cron_file) |
1053 | - f.close() |
1054 | - nrpe.add_check( |
1055 | - shortname=svc, |
1056 | - description='process check {%s}' % unit_name, |
1057 | - check_cmd='check_status_file.py -f ' |
1058 | - '/var/lib/nagios/service-check-%s.txt' % svc, |
1059 | - ) |
1060 | - |
1061 | - |
1062 | -def copy_nrpe_checks(): |
1063 | - """ |
1064 | - Copy the nrpe checks into place |
1065 | - |
1066 | - """ |
1067 | - NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins' |
1068 | - nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks', |
1069 | - 'charmhelpers', 'contrib', 'openstack', |
1070 | - 'files') |
1071 | - |
1072 | - if not os.path.exists(NAGIOS_PLUGINS): |
1073 | - os.makedirs(NAGIOS_PLUGINS) |
1074 | - for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")): |
1075 | - if os.path.isfile(fname): |
1076 | - shutil.copy2(fname, |
1077 | - os.path.join(NAGIOS_PLUGINS, os.path.basename(fname))) |
1078 | - |
1079 | - |
1080 | -def add_haproxy_checks(nrpe, unit_name): |
1081 | - """ |
1082 | - Add checks for each service in list |
1083 | - |
1084 | - :param NRPE nrpe: NRPE object to add check to |
1085 | - :param str unit_name: Unit name to use in check description |
1086 | - """ |
1087 | - nrpe.add_check( |
1088 | - shortname='haproxy_servers', |
1089 | - description='Check HAProxy {%s}' % unit_name, |
1090 | - check_cmd='check_haproxy.sh') |
1091 | - nrpe.add_check( |
1092 | - shortname='haproxy_queue', |
1093 | - description='Check HAProxy queue depth {%s}' % unit_name, |
1094 | - check_cmd='check_haproxy_queue_depth.sh') |
1095 | |
1096 | === removed file 'hooks/charmhelpers/contrib/charmsupport/volumes.py' |
1097 | --- hooks/charmhelpers/contrib/charmsupport/volumes.py 2015-07-29 18:35:16 +0000 |
1098 | +++ hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000 |
1099 | @@ -1,175 +0,0 @@ |
1100 | -# Copyright 2014-2015 Canonical Limited. |
1101 | -# |
1102 | -# This file is part of charm-helpers. |
1103 | -# |
1104 | -# charm-helpers is free software: you can redistribute it and/or modify |
1105 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1106 | -# published by the Free Software Foundation. |
1107 | -# |
1108 | -# charm-helpers is distributed in the hope that it will be useful, |
1109 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1110 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1111 | -# GNU Lesser General Public License for more details. |
1112 | -# |
1113 | -# You should have received a copy of the GNU Lesser General Public License |
1114 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1115 | - |
1116 | -''' |
1117 | -Functions for managing volumes in juju units. One volume is supported per unit. |
1118 | -Subordinates may have their own storage, provided it is on its own partition. |
1119 | - |
1120 | -Configuration stanzas:: |
1121 | - |
1122 | - volume-ephemeral: |
1123 | - type: boolean |
1124 | - default: true |
1125 | - description: > |
1126 | - If false, a volume is mounted as sepecified in "volume-map" |
1127 | - If true, ephemeral storage will be used, meaning that log data |
1128 | - will only exist as long as the machine. YOU HAVE BEEN WARNED. |
1129 | - volume-map: |
1130 | - type: string |
1131 | - default: {} |
1132 | - description: > |
1133 | - YAML map of units to device names, e.g: |
1134 | - "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }" |
1135 | - Service units will raise a configure-error if volume-ephemeral |
1136 | - is 'true' and no volume-map value is set. Use 'juju set' to set a |
1137 | - value and 'juju resolved' to complete configuration. |
1138 | - |
1139 | -Usage:: |
1140 | - |
1141 | - from charmsupport.volumes import configure_volume, VolumeConfigurationError |
1142 | - from charmsupport.hookenv import log, ERROR |
1143 | - def post_mount_hook(): |
1144 | - stop_service('myservice') |
1145 | - def post_mount_hook(): |
1146 | - start_service('myservice') |
1147 | - |
1148 | - if __name__ == '__main__': |
1149 | - try: |
1150 | - configure_volume(before_change=pre_mount_hook, |
1151 | - after_change=post_mount_hook) |
1152 | - except VolumeConfigurationError: |
1153 | - log('Storage could not be configured', ERROR) |
1154 | - |
1155 | -''' |
1156 | - |
1157 | -# XXX: Known limitations |
1158 | -# - fstab is neither consulted nor updated |
1159 | - |
1160 | -import os |
1161 | -from charmhelpers.core import hookenv |
1162 | -from charmhelpers.core import host |
1163 | -import yaml |
1164 | - |
1165 | - |
1166 | -MOUNT_BASE = '/srv/juju/volumes' |
1167 | - |
1168 | - |
1169 | -class VolumeConfigurationError(Exception): |
1170 | - '''Volume configuration data is missing or invalid''' |
1171 | - pass |
1172 | - |
1173 | - |
1174 | -def get_config(): |
1175 | - '''Gather and sanity-check volume configuration data''' |
1176 | - volume_config = {} |
1177 | - config = hookenv.config() |
1178 | - |
1179 | - errors = False |
1180 | - |
1181 | - if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'): |
1182 | - volume_config['ephemeral'] = True |
1183 | - else: |
1184 | - volume_config['ephemeral'] = False |
1185 | - |
1186 | - try: |
1187 | - volume_map = yaml.safe_load(config.get('volume-map', '{}')) |
1188 | - except yaml.YAMLError as e: |
1189 | - hookenv.log("Error parsing YAML volume-map: {}".format(e), |
1190 | - hookenv.ERROR) |
1191 | - errors = True |
1192 | - if volume_map is None: |
1193 | - # probably an empty string |
1194 | - volume_map = {} |
1195 | - elif not isinstance(volume_map, dict): |
1196 | - hookenv.log("Volume-map should be a dictionary, not {}".format( |
1197 | - type(volume_map))) |
1198 | - errors = True |
1199 | - |
1200 | - volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME']) |
1201 | - if volume_config['device'] and volume_config['ephemeral']: |
1202 | - # asked for ephemeral storage but also defined a volume ID |
1203 | - hookenv.log('A volume is defined for this unit, but ephemeral ' |
1204 | - 'storage was requested', hookenv.ERROR) |
1205 | - errors = True |
1206 | - elif not volume_config['device'] and not volume_config['ephemeral']: |
1207 | - # asked for permanent storage but did not define volume ID |
1208 | - hookenv.log('Ephemeral storage was requested, but there is no volume ' |
1209 | - 'defined for this unit.', hookenv.ERROR) |
1210 | - errors = True |
1211 | - |
1212 | - unit_mount_name = hookenv.local_unit().replace('/', '-') |
1213 | - volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name) |
1214 | - |
1215 | - if errors: |
1216 | - return None |
1217 | - return volume_config |
1218 | - |
1219 | - |
1220 | -def mount_volume(config): |
1221 | - if os.path.exists(config['mountpoint']): |
1222 | - if not os.path.isdir(config['mountpoint']): |
1223 | - hookenv.log('Not a directory: {}'.format(config['mountpoint'])) |
1224 | - raise VolumeConfigurationError() |
1225 | - else: |
1226 | - host.mkdir(config['mountpoint']) |
1227 | - if os.path.ismount(config['mountpoint']): |
1228 | - unmount_volume(config) |
1229 | - if not host.mount(config['device'], config['mountpoint'], persist=True): |
1230 | - raise VolumeConfigurationError() |
1231 | - |
1232 | - |
1233 | -def unmount_volume(config): |
1234 | - if os.path.ismount(config['mountpoint']): |
1235 | - if not host.umount(config['mountpoint'], persist=True): |
1236 | - raise VolumeConfigurationError() |
1237 | - |
1238 | - |
1239 | -def managed_mounts(): |
1240 | - '''List of all mounted managed volumes''' |
1241 | - return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts()) |
1242 | - |
1243 | - |
1244 | -def configure_volume(before_change=lambda: None, after_change=lambda: None): |
1245 | - '''Set up storage (or don't) according to the charm's volume configuration. |
1246 | - Returns the mount point or "ephemeral". before_change and after_change |
1247 | - are optional functions to be called if the volume configuration changes. |
1248 | - ''' |
1249 | - |
1250 | - config = get_config() |
1251 | - if not config: |
1252 | - hookenv.log('Failed to read volume configuration', hookenv.CRITICAL) |
1253 | - raise VolumeConfigurationError() |
1254 | - |
1255 | - if config['ephemeral']: |
1256 | - if os.path.ismount(config['mountpoint']): |
1257 | - before_change() |
1258 | - unmount_volume(config) |
1259 | - after_change() |
1260 | - return 'ephemeral' |
1261 | - else: |
1262 | - # persistent storage |
1263 | - if os.path.ismount(config['mountpoint']): |
1264 | - mounts = dict(managed_mounts()) |
1265 | - if mounts.get(config['mountpoint']) != config['device']: |
1266 | - before_change() |
1267 | - unmount_volume(config) |
1268 | - mount_volume(config) |
1269 | - after_change() |
1270 | - else: |
1271 | - before_change() |
1272 | - mount_volume(config) |
1273 | - after_change() |
1274 | - return config['mountpoint'] |
1275 | |
1276 | === removed directory 'hooks/charmhelpers/contrib/database' |
1277 | === removed file 'hooks/charmhelpers/contrib/database/__init__.py' |
1278 | === removed file 'hooks/charmhelpers/contrib/database/mysql.py' |
1279 | --- hooks/charmhelpers/contrib/database/mysql.py 2015-07-29 18:35:16 +0000 |
1280 | +++ hooks/charmhelpers/contrib/database/mysql.py 1970-01-01 00:00:00 +0000 |
1281 | @@ -1,412 +0,0 @@ |
1282 | -"""Helper for working with a MySQL database""" |
1283 | -import json |
1284 | -import re |
1285 | -import sys |
1286 | -import platform |
1287 | -import os |
1288 | -import glob |
1289 | - |
1290 | -# from string import upper |
1291 | - |
1292 | -from charmhelpers.core.host import ( |
1293 | - mkdir, |
1294 | - pwgen, |
1295 | - write_file |
1296 | -) |
1297 | -from charmhelpers.core.hookenv import ( |
1298 | - config as config_get, |
1299 | - relation_get, |
1300 | - related_units, |
1301 | - unit_get, |
1302 | - log, |
1303 | - DEBUG, |
1304 | - INFO, |
1305 | - WARNING, |
1306 | -) |
1307 | -from charmhelpers.fetch import ( |
1308 | - apt_install, |
1309 | - apt_update, |
1310 | - filter_installed_packages, |
1311 | -) |
1312 | -from charmhelpers.contrib.peerstorage import ( |
1313 | - peer_store, |
1314 | - peer_retrieve, |
1315 | -) |
1316 | -from charmhelpers.contrib.network.ip import get_host_ip |
1317 | - |
1318 | -try: |
1319 | - import MySQLdb |
1320 | -except ImportError: |
1321 | - apt_update(fatal=True) |
1322 | - apt_install(filter_installed_packages(['python-mysqldb']), fatal=True) |
1323 | - import MySQLdb |
1324 | - |
1325 | - |
1326 | -class MySQLHelper(object): |
1327 | - |
1328 | - def __init__(self, rpasswdf_template, upasswdf_template, host='localhost', |
1329 | - migrate_passwd_to_peer_relation=True, |
1330 | - delete_ondisk_passwd_file=True): |
1331 | - self.host = host |
1332 | - # Password file path templates |
1333 | - self.root_passwd_file_template = rpasswdf_template |
1334 | - self.user_passwd_file_template = upasswdf_template |
1335 | - |
1336 | - self.migrate_passwd_to_peer_relation = migrate_passwd_to_peer_relation |
1337 | - # If we migrate we have the option to delete local copy of root passwd |
1338 | - self.delete_ondisk_passwd_file = delete_ondisk_passwd_file |
1339 | - |
1340 | - def connect(self, user='root', password=None): |
1341 | - log("Opening db connection for %s@%s" % (user, self.host), level=DEBUG) |
1342 | - self.connection = MySQLdb.connect(user=user, host=self.host, |
1343 | - passwd=password) |
1344 | - |
1345 | - def database_exists(self, db_name): |
1346 | - cursor = self.connection.cursor() |
1347 | - try: |
1348 | - cursor.execute("SHOW DATABASES") |
1349 | - databases = [i[0] for i in cursor.fetchall()] |
1350 | - finally: |
1351 | - cursor.close() |
1352 | - |
1353 | - return db_name in databases |
1354 | - |
1355 | - def create_database(self, db_name): |
1356 | - cursor = self.connection.cursor() |
1357 | - try: |
1358 | - cursor.execute("CREATE DATABASE {} CHARACTER SET UTF8" |
1359 | - .format(db_name)) |
1360 | - finally: |
1361 | - cursor.close() |
1362 | - |
1363 | - def grant_exists(self, db_name, db_user, remote_ip): |
1364 | - cursor = self.connection.cursor() |
1365 | - priv_string = "GRANT ALL PRIVILEGES ON `{}`.* " \ |
1366 | - "TO '{}'@'{}'".format(db_name, db_user, remote_ip) |
1367 | - try: |
1368 | - cursor.execute("SHOW GRANTS for '{}'@'{}'".format(db_user, |
1369 | - remote_ip)) |
1370 | - grants = [i[0] for i in cursor.fetchall()] |
1371 | - except MySQLdb.OperationalError: |
1372 | - return False |
1373 | - finally: |
1374 | - cursor.close() |
1375 | - |
1376 | - # TODO: review for different grants |
1377 | - return priv_string in grants |
1378 | - |
1379 | - def create_grant(self, db_name, db_user, remote_ip, password): |
1380 | - cursor = self.connection.cursor() |
1381 | - try: |
1382 | - # TODO: review for different grants |
1383 | - cursor.execute("GRANT ALL PRIVILEGES ON {}.* TO '{}'@'{}' " |
1384 | - "IDENTIFIED BY '{}'".format(db_name, |
1385 | - db_user, |
1386 | - remote_ip, |
1387 | - password)) |
1388 | - finally: |
1389 | - cursor.close() |
1390 | - |
1391 | - def create_admin_grant(self, db_user, remote_ip, password): |
1392 | - cursor = self.connection.cursor() |
1393 | - try: |
1394 | - cursor.execute("GRANT ALL PRIVILEGES ON *.* TO '{}'@'{}' " |
1395 | - "IDENTIFIED BY '{}'".format(db_user, |
1396 | - remote_ip, |
1397 | - password)) |
1398 | - finally: |
1399 | - cursor.close() |
1400 | - |
1401 | - def cleanup_grant(self, db_user, remote_ip): |
1402 | - cursor = self.connection.cursor() |
1403 | - try: |
1404 | - cursor.execute("DROP FROM mysql.user WHERE user='{}' " |
1405 | - "AND HOST='{}'".format(db_user, |
1406 | - remote_ip)) |
1407 | - finally: |
1408 | - cursor.close() |
1409 | - |
1410 | - def execute(self, sql): |
1411 | - """Execute arbitary SQL against the database.""" |
1412 | - cursor = self.connection.cursor() |
1413 | - try: |
1414 | - cursor.execute(sql) |
1415 | - finally: |
1416 | - cursor.close() |
1417 | - |
1418 | - def migrate_passwords_to_peer_relation(self, excludes=None): |
1419 | - """Migrate any passwords storage on disk to cluster peer relation.""" |
1420 | - dirname = os.path.dirname(self.root_passwd_file_template) |
1421 | - path = os.path.join(dirname, '*.passwd') |
1422 | - for f in glob.glob(path): |
1423 | - if excludes and f in excludes: |
1424 | - log("Excluding %s from peer migration" % (f), level=DEBUG) |
1425 | - continue |
1426 | - |
1427 | - key = os.path.basename(f) |
1428 | - with open(f, 'r') as passwd: |
1429 | - _value = passwd.read().strip() |
1430 | - |
1431 | - try: |
1432 | - peer_store(key, _value) |
1433 | - |
1434 | - if self.delete_ondisk_passwd_file: |
1435 | - os.unlink(f) |
1436 | - except ValueError: |
1437 | - # NOTE cluster relation not yet ready - skip for now |
1438 | - pass |
1439 | - |
1440 | - def get_mysql_password_on_disk(self, username=None, password=None): |
1441 | - """Retrieve, generate or store a mysql password for the provided |
1442 | - username on disk.""" |
1443 | - if username: |
1444 | - template = self.user_passwd_file_template |
1445 | - passwd_file = template.format(username) |
1446 | - else: |
1447 | - passwd_file = self.root_passwd_file_template |
1448 | - |
1449 | - _password = None |
1450 | - if os.path.exists(passwd_file): |
1451 | - log("Using existing password file '%s'" % passwd_file, level=DEBUG) |
1452 | - with open(passwd_file, 'r') as passwd: |
1453 | - _password = passwd.read().strip() |
1454 | - else: |
1455 | - log("Generating new password file '%s'" % passwd_file, level=DEBUG) |
1456 | - if not os.path.isdir(os.path.dirname(passwd_file)): |
1457 | - # NOTE: need to ensure this is not mysql root dir (which needs |
1458 | - # to be mysql readable) |
1459 | - mkdir(os.path.dirname(passwd_file), owner='root', group='root', |
1460 | - perms=0o770) |
1461 | - # Force permissions - for some reason the chmod in makedirs |
1462 | - # fails |
1463 | - os.chmod(os.path.dirname(passwd_file), 0o770) |
1464 | - |
1465 | - _password = password or pwgen(length=32) |
1466 | - write_file(passwd_file, _password, owner='root', group='root', |
1467 | - perms=0o660) |
1468 | - |
1469 | - return _password |
1470 | - |
1471 | - def passwd_keys(self, username): |
1472 | - """Generator to return keys used to store passwords in peer store. |
1473 | - |
1474 | - NOTE: we support both legacy and new format to support mysql |
1475 | - charm prior to refactor. This is necessary to avoid LP 1451890. |
1476 | - """ |
1477 | - keys = [] |
1478 | - if username == 'mysql': |
1479 | - log("Bad username '%s'" % (username), level=WARNING) |
1480 | - |
1481 | - if username: |
1482 | - # IMPORTANT: *newer* format must be returned first |
1483 | - keys.append('mysql-%s.passwd' % (username)) |
1484 | - keys.append('%s.passwd' % (username)) |
1485 | - else: |
1486 | - keys.append('mysql.passwd') |
1487 | - |
1488 | - for key in keys: |
1489 | - yield key |
1490 | - |
1491 | - def get_mysql_password(self, username=None, password=None): |
1492 | - """Retrieve, generate or store a mysql password for the provided |
1493 | - username using peer relation cluster.""" |
1494 | - excludes = [] |
1495 | - |
1496 | - # First check peer relation. |
1497 | - try: |
1498 | - for key in self.passwd_keys(username): |
1499 | - _password = peer_retrieve(key) |
1500 | - if _password: |
1501 | - break |
1502 | - |
1503 | - # If root password available don't update peer relation from local |
1504 | - if _password and not username: |
1505 | - excludes.append(self.root_passwd_file_template) |
1506 | - |
1507 | - except ValueError: |
1508 | - # cluster relation is not yet started; use on-disk |
1509 | - _password = None |
1510 | - |
1511 | - # If none available, generate new one |
1512 | - if not _password: |
1513 | - _password = self.get_mysql_password_on_disk(username, password) |
1514 | - |
1515 | - # Put on wire if required |
1516 | - if self.migrate_passwd_to_peer_relation: |
1517 | - self.migrate_passwords_to_peer_relation(excludes=excludes) |
1518 | - |
1519 | - return _password |
1520 | - |
1521 | - def get_mysql_root_password(self, password=None): |
1522 | - """Retrieve or generate mysql root password for service units.""" |
1523 | - return self.get_mysql_password(username=None, password=password) |
1524 | - |
1525 | - def normalize_address(self, hostname): |
1526 | - """Ensure that address returned is an IP address (i.e. not fqdn)""" |
1527 | - if config_get('prefer-ipv6'): |
1528 | - # TODO: add support for ipv6 dns |
1529 | - return hostname |
1530 | - |
1531 | - if hostname != unit_get('private-address'): |
1532 | - return get_host_ip(hostname, fallback=hostname) |
1533 | - |
1534 | - # Otherwise assume localhost |
1535 | - return '127.0.0.1' |
1536 | - |
1537 | - def get_allowed_units(self, database, username, relation_id=None): |
1538 | - """Get list of units with access grants for database with username. |
1539 | - |
1540 | - This is typically used to provide shared-db relations with a list of |
1541 | - which units have been granted access to the given database. |
1542 | - """ |
1543 | - self.connect(password=self.get_mysql_root_password()) |
1544 | - allowed_units = set() |
1545 | - for unit in related_units(relation_id): |
1546 | - settings = relation_get(rid=relation_id, unit=unit) |
1547 | - # First check for setting with prefix, then without |
1548 | - for attr in ["%s_hostname" % (database), 'hostname']: |
1549 | - hosts = settings.get(attr, None) |
1550 | - if hosts: |
1551 | - break |
1552 | - |
1553 | - if hosts: |
1554 | - # hostname can be json-encoded list of hostnames |
1555 | - try: |
1556 | - hosts = json.loads(hosts) |
1557 | - except ValueError: |
1558 | - hosts = [hosts] |
1559 | - else: |
1560 | - hosts = [settings['private-address']] |
1561 | - |
1562 | - if hosts: |
1563 | - for host in hosts: |
1564 | - host = self.normalize_address(host) |
1565 | - if self.grant_exists(database, username, host): |
1566 | - log("Grant exists for host '%s' on db '%s'" % |
1567 | - (host, database), level=DEBUG) |
1568 | - if unit not in allowed_units: |
1569 | - allowed_units.add(unit) |
1570 | - else: |
1571 | - log("Grant does NOT exist for host '%s' on db '%s'" % |
1572 | - (host, database), level=DEBUG) |
1573 | - else: |
1574 | - log("No hosts found for grant check", level=INFO) |
1575 | - |
1576 | - return allowed_units |
1577 | - |
1578 | - def configure_db(self, hostname, database, username, admin=False): |
1579 | - """Configure access to database for username from hostname.""" |
1580 | - self.connect(password=self.get_mysql_root_password()) |
1581 | - if not self.database_exists(database): |
1582 | - self.create_database(database) |
1583 | - |
1584 | - remote_ip = self.normalize_address(hostname) |
1585 | - password = self.get_mysql_password(username) |
1586 | - if not self.grant_exists(database, username, remote_ip): |
1587 | - if not admin: |
1588 | - self.create_grant(database, username, remote_ip, password) |
1589 | - else: |
1590 | - self.create_admin_grant(username, remote_ip, password) |
1591 | - |
1592 | - return password |
1593 | - |
1594 | - |
1595 | -class PerconaClusterHelper(object): |
1596 | - |
1597 | - # Going for the biggest page size to avoid wasted bytes. |
1598 | - # InnoDB page size is 16MB |
1599 | - |
1600 | - DEFAULT_PAGE_SIZE = 16 * 1024 * 1024 |
1601 | - DEFAULT_INNODB_BUFFER_FACTOR = 0.50 |
1602 | - |
1603 | - def human_to_bytes(self, human): |
1604 | - """Convert human readable configuration options to bytes.""" |
1605 | - num_re = re.compile('^[0-9]+$') |
1606 | - if num_re.match(human): |
1607 | - return human |
1608 | - |
1609 | - factors = { |
1610 | - 'K': 1024, |
1611 | - 'M': 1048576, |
1612 | - 'G': 1073741824, |
1613 | - 'T': 1099511627776 |
1614 | - } |
1615 | - modifier = human[-1] |
1616 | - if modifier in factors: |
1617 | - return int(human[:-1]) * factors[modifier] |
1618 | - |
1619 | - if modifier == '%': |
1620 | - total_ram = self.human_to_bytes(self.get_mem_total()) |
1621 | - if self.is_32bit_system() and total_ram > self.sys_mem_limit(): |
1622 | - total_ram = self.sys_mem_limit() |
1623 | - factor = int(human[:-1]) * 0.01 |
1624 | - pctram = total_ram * factor |
1625 | - return int(pctram - (pctram % self.DEFAULT_PAGE_SIZE)) |
1626 | - |
1627 | - raise ValueError("Can only convert K,M,G, or T") |
1628 | - |
1629 | - def is_32bit_system(self): |
1630 | - """Determine whether system is 32 or 64 bit.""" |
1631 | - try: |
1632 | - return sys.maxsize < 2 ** 32 |
1633 | - except OverflowError: |
1634 | - return False |
1635 | - |
1636 | - def sys_mem_limit(self): |
1637 | - """Determine the default memory limit for the current service unit.""" |
1638 | - if platform.machine() in ['armv7l']: |
1639 | - _mem_limit = self.human_to_bytes('2700M') # experimentally determined |
1640 | - else: |
1641 | - # Limit for x86 based 32bit systems |
1642 | - _mem_limit = self.human_to_bytes('4G') |
1643 | - |
1644 | - return _mem_limit |
1645 | - |
1646 | - def get_mem_total(self): |
1647 | - """Calculate the total memory in the current service unit.""" |
1648 | - with open('/proc/meminfo') as meminfo_file: |
1649 | - for line in meminfo_file: |
1650 | - key, mem = line.split(':', 2) |
1651 | - if key == 'MemTotal': |
1652 | - mtot, modifier = mem.strip().split(' ') |
1653 | - return '%s%s' % (mtot, modifier[0].upper()) |
1654 | - |
1655 | - def parse_config(self): |
1656 | - """Parse charm configuration and calculate values for config files.""" |
1657 | - config = config_get() |
1658 | - mysql_config = {} |
1659 | - if 'max-connections' in config: |
1660 | - mysql_config['max_connections'] = config['max-connections'] |
1661 | - |
1662 | - if 'wait-timeout' in config: |
1663 | - mysql_config['wait_timeout'] = config['wait-timeout'] |
1664 | - |
1665 | - if 'innodb-flush-log-at-trx-commit' in config: |
1666 | - mysql_config['innodb_flush_log_at_trx_commit'] = config['innodb-flush-log-at-trx-commit'] |
1667 | - |
1668 | - # Set a sane default key_buffer size |
1669 | - mysql_config['key_buffer'] = self.human_to_bytes('32M') |
1670 | - total_memory = self.human_to_bytes(self.get_mem_total()) |
1671 | - |
1672 | - dataset_bytes = config.get('dataset-size', None) |
1673 | - innodb_buffer_pool_size = config.get('innodb-buffer-pool-size', None) |
1674 | - |
1675 | - if innodb_buffer_pool_size: |
1676 | - innodb_buffer_pool_size = self.human_to_bytes( |
1677 | - innodb_buffer_pool_size) |
1678 | - elif dataset_bytes: |
1679 | - log("Option 'dataset-size' has been deprecated, please use" |
1680 | - "innodb_buffer_pool_size option instead", level="WARN") |
1681 | - innodb_buffer_pool_size = self.human_to_bytes( |
1682 | - dataset_bytes) |
1683 | - else: |
1684 | - innodb_buffer_pool_size = int( |
1685 | - total_memory * self.DEFAULT_INNODB_BUFFER_FACTOR) |
1686 | - |
1687 | - if innodb_buffer_pool_size > total_memory: |
1688 | - log("innodb_buffer_pool_size; {} is greater than system available memory:{}".format( |
1689 | - innodb_buffer_pool_size, |
1690 | - total_memory), level='WARN') |
1691 | - |
1692 | - mysql_config['innodb_buffer_pool_size'] = innodb_buffer_pool_size |
1693 | - return mysql_config |
1694 | |
1695 | === removed directory 'hooks/charmhelpers/contrib/hardening' |
1696 | === removed file 'hooks/charmhelpers/contrib/hardening/__init__.py' |
1697 | --- hooks/charmhelpers/contrib/hardening/__init__.py 2016-04-22 04:35:32 +0000 |
1698 | +++ hooks/charmhelpers/contrib/hardening/__init__.py 1970-01-01 00:00:00 +0000 |
1699 | @@ -1,15 +0,0 @@ |
1700 | -# Copyright 2016 Canonical Limited. |
1701 | -# |
1702 | -# This file is part of charm-helpers. |
1703 | -# |
1704 | -# charm-helpers is free software: you can redistribute it and/or modify |
1705 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1706 | -# published by the Free Software Foundation. |
1707 | -# |
1708 | -# charm-helpers is distributed in the hope that it will be useful, |
1709 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1710 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1711 | -# GNU Lesser General Public License for more details. |
1712 | -# |
1713 | -# You should have received a copy of the GNU Lesser General Public License |
1714 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1715 | |
1716 | === removed directory 'hooks/charmhelpers/contrib/hardening/apache' |
1717 | === removed file 'hooks/charmhelpers/contrib/hardening/apache/__init__.py' |
1718 | --- hooks/charmhelpers/contrib/hardening/apache/__init__.py 2016-04-22 04:35:32 +0000 |
1719 | +++ hooks/charmhelpers/contrib/hardening/apache/__init__.py 1970-01-01 00:00:00 +0000 |
1720 | @@ -1,19 +0,0 @@ |
1721 | -# Copyright 2016 Canonical Limited. |
1722 | -# |
1723 | -# This file is part of charm-helpers. |
1724 | -# |
1725 | -# charm-helpers is free software: you can redistribute it and/or modify |
1726 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1727 | -# published by the Free Software Foundation. |
1728 | -# |
1729 | -# charm-helpers is distributed in the hope that it will be useful, |
1730 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1731 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1732 | -# GNU Lesser General Public License for more details. |
1733 | -# |
1734 | -# You should have received a copy of the GNU Lesser General Public License |
1735 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1736 | - |
1737 | -from os import path |
1738 | - |
1739 | -TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') |
1740 | |
1741 | === removed directory 'hooks/charmhelpers/contrib/hardening/apache/checks' |
1742 | === removed file 'hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py' |
1743 | --- hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py 2016-04-22 04:35:32 +0000 |
1744 | +++ hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py 1970-01-01 00:00:00 +0000 |
1745 | @@ -1,31 +0,0 @@ |
1746 | -# Copyright 2016 Canonical Limited. |
1747 | -# |
1748 | -# This file is part of charm-helpers. |
1749 | -# |
1750 | -# charm-helpers is free software: you can redistribute it and/or modify |
1751 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1752 | -# published by the Free Software Foundation. |
1753 | -# |
1754 | -# charm-helpers is distributed in the hope that it will be useful, |
1755 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1756 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1757 | -# GNU Lesser General Public License for more details. |
1758 | -# |
1759 | -# You should have received a copy of the GNU Lesser General Public License |
1760 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1761 | - |
1762 | -from charmhelpers.core.hookenv import ( |
1763 | - log, |
1764 | - DEBUG, |
1765 | -) |
1766 | -from charmhelpers.contrib.hardening.apache.checks import config |
1767 | - |
1768 | - |
1769 | -def run_apache_checks(): |
1770 | - log("Starting Apache hardening checks.", level=DEBUG) |
1771 | - checks = config.get_audits() |
1772 | - for check in checks: |
1773 | - log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) |
1774 | - check.ensure_compliance() |
1775 | - |
1776 | - log("Apache hardening checks complete.", level=DEBUG) |
1777 | |
1778 | === removed file 'hooks/charmhelpers/contrib/hardening/apache/checks/config.py' |
1779 | --- hooks/charmhelpers/contrib/hardening/apache/checks/config.py 2016-04-22 04:35:32 +0000 |
1780 | +++ hooks/charmhelpers/contrib/hardening/apache/checks/config.py 1970-01-01 00:00:00 +0000 |
1781 | @@ -1,100 +0,0 @@ |
1782 | -# Copyright 2016 Canonical Limited. |
1783 | -# |
1784 | -# This file is part of charm-helpers. |
1785 | -# |
1786 | -# charm-helpers is free software: you can redistribute it and/or modify |
1787 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1788 | -# published by the Free Software Foundation. |
1789 | -# |
1790 | -# charm-helpers is distributed in the hope that it will be useful, |
1791 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1792 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1793 | -# GNU Lesser General Public License for more details. |
1794 | -# |
1795 | -# You should have received a copy of the GNU Lesser General Public License |
1796 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1797 | - |
1798 | -import os |
1799 | -import re |
1800 | -import subprocess |
1801 | - |
1802 | - |
1803 | -from charmhelpers.core.hookenv import ( |
1804 | - log, |
1805 | - INFO, |
1806 | -) |
1807 | -from charmhelpers.contrib.hardening.audits.file import ( |
1808 | - FilePermissionAudit, |
1809 | - DirectoryPermissionAudit, |
1810 | - NoReadWriteForOther, |
1811 | - TemplatedFile, |
1812 | -) |
1813 | -from charmhelpers.contrib.hardening.audits.apache import DisabledModuleAudit |
1814 | -from charmhelpers.contrib.hardening.apache import TEMPLATES_DIR |
1815 | -from charmhelpers.contrib.hardening import utils |
1816 | - |
1817 | - |
1818 | -def get_audits(): |
1819 | - """Get Apache hardening config audits. |
1820 | - |
1821 | - :returns: dictionary of audits |
1822 | - """ |
1823 | - if subprocess.call(['which', 'apache2'], stdout=subprocess.PIPE) != 0: |
1824 | - log("Apache server does not appear to be installed on this node - " |
1825 | - "skipping apache hardening", level=INFO) |
1826 | - return [] |
1827 | - |
1828 | - context = ApacheConfContext() |
1829 | - settings = utils.get_settings('apache') |
1830 | - audits = [ |
1831 | - FilePermissionAudit(paths='/etc/apache2/apache2.conf', user='root', |
1832 | - group='root', mode=0o0640), |
1833 | - |
1834 | - TemplatedFile(os.path.join(settings['common']['apache_dir'], |
1835 | - 'mods-available/alias.conf'), |
1836 | - context, |
1837 | - TEMPLATES_DIR, |
1838 | - mode=0o0755, |
1839 | - user='root', |
1840 | - service_actions=[{'service': 'apache2', |
1841 | - 'actions': ['restart']}]), |
1842 | - |
1843 | - TemplatedFile(os.path.join(settings['common']['apache_dir'], |
1844 | - 'conf-enabled/hardening.conf'), |
1845 | - context, |
1846 | - TEMPLATES_DIR, |
1847 | - mode=0o0640, |
1848 | - user='root', |
1849 | - service_actions=[{'service': 'apache2', |
1850 | - 'actions': ['restart']}]), |
1851 | - |
1852 | - DirectoryPermissionAudit(settings['common']['apache_dir'], |
1853 | - user='root', |
1854 | - group='root', |
1855 | - mode=0o640), |
1856 | - |
1857 | - DisabledModuleAudit(settings['hardening']['modules_to_disable']), |
1858 | - |
1859 | - NoReadWriteForOther(settings['common']['apache_dir']), |
1860 | - ] |
1861 | - |
1862 | - return audits |
1863 | - |
1864 | - |
1865 | -class ApacheConfContext(object): |
1866 | - """Defines the set of key/value pairs to set in a apache config file. |
1867 | - |
1868 | - This context, when called, will return a dictionary containing the |
1869 | - key/value pairs of setting to specify in the |
1870 | - /etc/apache/conf-enabled/hardening.conf file. |
1871 | - """ |
1872 | - def __call__(self): |
1873 | - settings = utils.get_settings('apache') |
1874 | - ctxt = settings['hardening'] |
1875 | - |
1876 | - out = subprocess.check_output(['apache2', '-v']) |
1877 | - ctxt['apache_version'] = re.search(r'.+version: Apache/(.+?)\s.+', |
1878 | - out).group(1) |
1879 | - ctxt['apache_icondir'] = '/usr/share/apache2/icons/' |
1880 | - ctxt['traceenable'] = settings['hardening']['traceenable'] |
1881 | - return ctxt |
1882 | |
1883 | === removed directory 'hooks/charmhelpers/contrib/hardening/audits' |
1884 | === removed file 'hooks/charmhelpers/contrib/hardening/audits/__init__.py' |
1885 | --- hooks/charmhelpers/contrib/hardening/audits/__init__.py 2016-04-22 04:35:32 +0000 |
1886 | +++ hooks/charmhelpers/contrib/hardening/audits/__init__.py 1970-01-01 00:00:00 +0000 |
1887 | @@ -1,63 +0,0 @@ |
1888 | -# Copyright 2016 Canonical Limited. |
1889 | -# |
1890 | -# This file is part of charm-helpers. |
1891 | -# |
1892 | -# charm-helpers is free software: you can redistribute it and/or modify |
1893 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1894 | -# published by the Free Software Foundation. |
1895 | -# |
1896 | -# charm-helpers is distributed in the hope that it will be useful, |
1897 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1898 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1899 | -# GNU Lesser General Public License for more details. |
1900 | -# |
1901 | -# You should have received a copy of the GNU Lesser General Public License |
1902 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1903 | - |
1904 | - |
1905 | -class BaseAudit(object): # NO-QA |
1906 | - """Base class for hardening checks. |
1907 | - |
1908 | - The lifecycle of a hardening check is to first check to see if the system |
1909 | - is in compliance for the specified check. If it is not in compliance, the |
1910 | - check method will return a value which will be supplied to the. |
1911 | - """ |
1912 | - def __init__(self, *args, **kwargs): |
1913 | - self.unless = kwargs.get('unless', None) |
1914 | - super(BaseAudit, self).__init__() |
1915 | - |
1916 | - def ensure_compliance(self): |
1917 | - """Checks to see if the current hardening check is in compliance or |
1918 | - not. |
1919 | - |
1920 | - If the check that is performed is not in compliance, then an exception |
1921 | - should be raised. |
1922 | - """ |
1923 | - pass |
1924 | - |
1925 | - def _take_action(self): |
1926 | - """Determines whether to perform the action or not. |
1927 | - |
1928 | - Checks whether or not an action should be taken. This is determined by |
1929 | - the truthy value for the unless parameter. If unless is a callback |
1930 | - method, it will be invoked with no parameters in order to determine |
1931 | - whether or not the action should be taken. Otherwise, the truthy value |
1932 | - of the unless attribute will determine if the action should be |
1933 | - performed. |
1934 | - """ |
1935 | - # Do the action if there isn't an unless override. |
1936 | - if self.unless is None: |
1937 | - return True |
1938 | - |
1939 | - # Invoke the callback if there is one. |
1940 | - if hasattr(self.unless, '__call__'): |
1941 | - results = self.unless() |
1942 | - if results: |
1943 | - return False |
1944 | - else: |
1945 | - return True |
1946 | - |
1947 | - if self.unless: |
1948 | - return False |
1949 | - else: |
1950 | - return True |
1951 | |
1952 | === removed file 'hooks/charmhelpers/contrib/hardening/audits/apache.py' |
1953 | --- hooks/charmhelpers/contrib/hardening/audits/apache.py 2016-04-22 04:35:32 +0000 |
1954 | +++ hooks/charmhelpers/contrib/hardening/audits/apache.py 1970-01-01 00:00:00 +0000 |
1955 | @@ -1,100 +0,0 @@ |
1956 | -# Copyright 2016 Canonical Limited. |
1957 | -# |
1958 | -# This file is part of charm-helpers. |
1959 | -# |
1960 | -# charm-helpers is free software: you can redistribute it and/or modify |
1961 | -# it under the terms of the GNU Lesser General Public License version 3 as |
1962 | -# published by the Free Software Foundation. |
1963 | -# |
1964 | -# charm-helpers is distributed in the hope that it will be useful, |
1965 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1966 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1967 | -# GNU Lesser General Public License for more details. |
1968 | -# |
1969 | -# You should have received a copy of the GNU Lesser General Public License |
1970 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1971 | - |
1972 | -import re |
1973 | -import subprocess |
1974 | - |
1975 | -from six import string_types |
1976 | - |
1977 | -from charmhelpers.core.hookenv import ( |
1978 | - log, |
1979 | - INFO, |
1980 | - ERROR, |
1981 | -) |
1982 | - |
1983 | -from charmhelpers.contrib.hardening.audits import BaseAudit |
1984 | - |
1985 | - |
1986 | -class DisabledModuleAudit(BaseAudit): |
1987 | - """Audits Apache2 modules. |
1988 | - |
1989 | - Determines if the apache2 modules are enabled. If the modules are enabled |
1990 | - then they are removed in the ensure_compliance. |
1991 | - """ |
1992 | - def __init__(self, modules): |
1993 | - if modules is None: |
1994 | - self.modules = [] |
1995 | - elif isinstance(modules, string_types): |
1996 | - self.modules = [modules] |
1997 | - else: |
1998 | - self.modules = modules |
1999 | - |
2000 | - def ensure_compliance(self): |
2001 | - """Ensures that the modules are not loaded.""" |
2002 | - if not self.modules: |
2003 | - return |
2004 | - |
2005 | - try: |
2006 | - loaded_modules = self._get_loaded_modules() |
2007 | - non_compliant_modules = [] |
2008 | - for module in self.modules: |
2009 | - if module in loaded_modules: |
2010 | - log("Module '%s' is enabled but should not be." % |
2011 | - (module), level=INFO) |
2012 | - non_compliant_modules.append(module) |
2013 | - |
2014 | - if len(non_compliant_modules) == 0: |
2015 | - return |
2016 | - |
2017 | - for module in non_compliant_modules: |
2018 | - self._disable_module(module) |
2019 | - self._restart_apache() |
2020 | - except subprocess.CalledProcessError as e: |
2021 | - log('Error occurred auditing apache module compliance. ' |
2022 | - 'This may have been already reported. ' |
2023 | - 'Output is: %s' % e.output, level=ERROR) |
2024 | - |
2025 | - @staticmethod |
2026 | - def _get_loaded_modules(): |
2027 | - """Returns the modules which are enabled in Apache.""" |
2028 | - output = subprocess.check_output(['apache2ctl', '-M']) |
2029 | - modules = [] |
2030 | - for line in output.strip().split(): |
2031 | - # Each line of the enabled module output looks like: |
2032 | - # module_name (static|shared) |
2033 | - # Plus a header line at the top of the output which is stripped |
2034 | - # out by the regex. |
2035 | - matcher = re.search(r'^ (\S*)', line) |
2036 | - if matcher: |
2037 | - modules.append(matcher.group(1)) |
2038 | - return modules |
2039 | - |
2040 | - @staticmethod |
2041 | - def _disable_module(module): |
2042 | - """Disables the specified module in Apache.""" |
2043 | - try: |
2044 | - subprocess.check_call(['a2dismod', module]) |
2045 | - except subprocess.CalledProcessError as e: |
2046 | - # Note: catch error here to allow the attempt of disabling |
2047 | - # multiple modules in one go rather than failing after the |
2048 | - # first module fails. |
2049 | - log('Error occurred disabling module %s. ' |
2050 | - 'Output is: %s' % (module, e.output), level=ERROR) |
2051 | - |
2052 | - @staticmethod |
2053 | - def _restart_apache(): |
2054 | - """Restarts the apache process""" |
2055 | - subprocess.check_output(['service', 'apache2', 'restart']) |
2056 | |
2057 | === removed file 'hooks/charmhelpers/contrib/hardening/audits/apt.py' |
2058 | --- hooks/charmhelpers/contrib/hardening/audits/apt.py 2016-04-22 04:35:32 +0000 |
2059 | +++ hooks/charmhelpers/contrib/hardening/audits/apt.py 1970-01-01 00:00:00 +0000 |
2060 | @@ -1,105 +0,0 @@ |
2061 | -# Copyright 2016 Canonical Limited. |
2062 | -# |
2063 | -# This file is part of charm-helpers. |
2064 | -# |
2065 | -# charm-helpers is free software: you can redistribute it and/or modify |
2066 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2067 | -# published by the Free Software Foundation. |
2068 | -# |
2069 | -# charm-helpers is distributed in the hope that it will be useful, |
2070 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2071 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2072 | -# GNU Lesser General Public License for more details. |
2073 | -# |
2074 | -# You should have received a copy of the GNU Lesser General Public License |
2075 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2076 | - |
2077 | -from __future__ import absolute_import # required for external apt import |
2078 | -from apt import apt_pkg |
2079 | -from six import string_types |
2080 | - |
2081 | -from charmhelpers.fetch import ( |
2082 | - apt_cache, |
2083 | - apt_purge |
2084 | -) |
2085 | -from charmhelpers.core.hookenv import ( |
2086 | - log, |
2087 | - DEBUG, |
2088 | - WARNING, |
2089 | -) |
2090 | -from charmhelpers.contrib.hardening.audits import BaseAudit |
2091 | - |
2092 | - |
2093 | -class AptConfig(BaseAudit): |
2094 | - |
2095 | - def __init__(self, config, **kwargs): |
2096 | - self.config = config |
2097 | - |
2098 | - def verify_config(self): |
2099 | - apt_pkg.init() |
2100 | - for cfg in self.config: |
2101 | - value = apt_pkg.config.get(cfg['key'], cfg.get('default', '')) |
2102 | - if value and value != cfg['expected']: |
2103 | - log("APT config '%s' has unexpected value '%s' " |
2104 | - "(expected='%s')" % |
2105 | - (cfg['key'], value, cfg['expected']), level=WARNING) |
2106 | - |
2107 | - def ensure_compliance(self): |
2108 | - self.verify_config() |
2109 | - |
2110 | - |
2111 | -class RestrictedPackages(BaseAudit): |
2112 | - """Class used to audit restricted packages on the system.""" |
2113 | - |
2114 | - def __init__(self, pkgs, **kwargs): |
2115 | - super(RestrictedPackages, self).__init__(**kwargs) |
2116 | - if isinstance(pkgs, string_types) or not hasattr(pkgs, '__iter__'): |
2117 | - self.pkgs = [pkgs] |
2118 | - else: |
2119 | - self.pkgs = pkgs |
2120 | - |
2121 | - def ensure_compliance(self): |
2122 | - cache = apt_cache() |
2123 | - |
2124 | - for p in self.pkgs: |
2125 | - if p not in cache: |
2126 | - continue |
2127 | - |
2128 | - pkg = cache[p] |
2129 | - if not self.is_virtual_package(pkg): |
2130 | - if not pkg.current_ver: |
2131 | - log("Package '%s' is not installed." % pkg.name, |
2132 | - level=DEBUG) |
2133 | - continue |
2134 | - else: |
2135 | - log("Restricted package '%s' is installed" % pkg.name, |
2136 | - level=WARNING) |
2137 | - self.delete_package(cache, pkg) |
2138 | - else: |
2139 | - log("Checking restricted virtual package '%s' provides" % |
2140 | - pkg.name, level=DEBUG) |
2141 | - self.delete_package(cache, pkg) |
2142 | - |
2143 | - def delete_package(self, cache, pkg): |
2144 | - """Deletes the package from the system. |
2145 | - |
2146 | - Deletes the package form the system, properly handling virtual |
2147 | - packages. |
2148 | - |
2149 | - :param cache: the apt cache |
2150 | - :param pkg: the package to remove |
2151 | - """ |
2152 | - if self.is_virtual_package(pkg): |
2153 | - log("Package '%s' appears to be virtual - purging provides" % |
2154 | - pkg.name, level=DEBUG) |
2155 | - for _p in pkg.provides_list: |
2156 | - self.delete_package(cache, _p[2].parent_pkg) |
2157 | - elif not pkg.current_ver: |
2158 | - log("Package '%s' not installed" % pkg.name, level=DEBUG) |
2159 | - return |
2160 | - else: |
2161 | - log("Purging package '%s'" % pkg.name, level=DEBUG) |
2162 | - apt_purge(pkg.name) |
2163 | - |
2164 | - def is_virtual_package(self, pkg): |
2165 | - return pkg.has_provides and not pkg.has_versions |
2166 | |
2167 | === removed file 'hooks/charmhelpers/contrib/hardening/audits/file.py' |
2168 | --- hooks/charmhelpers/contrib/hardening/audits/file.py 2016-04-22 04:35:32 +0000 |
2169 | +++ hooks/charmhelpers/contrib/hardening/audits/file.py 1970-01-01 00:00:00 +0000 |
2170 | @@ -1,552 +0,0 @@ |
2171 | -# Copyright 2016 Canonical Limited. |
2172 | -# |
2173 | -# This file is part of charm-helpers. |
2174 | -# |
2175 | -# charm-helpers is free software: you can redistribute it and/or modify |
2176 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2177 | -# published by the Free Software Foundation. |
2178 | -# |
2179 | -# charm-helpers is distributed in the hope that it will be useful, |
2180 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2181 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2182 | -# GNU Lesser General Public License for more details. |
2183 | -# |
2184 | -# You should have received a copy of the GNU Lesser General Public License |
2185 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2186 | - |
2187 | -import grp |
2188 | -import os |
2189 | -import pwd |
2190 | -import re |
2191 | - |
2192 | -from subprocess import ( |
2193 | - CalledProcessError, |
2194 | - check_output, |
2195 | - check_call, |
2196 | -) |
2197 | -from traceback import format_exc |
2198 | -from six import string_types |
2199 | -from stat import ( |
2200 | - S_ISGID, |
2201 | - S_ISUID |
2202 | -) |
2203 | - |
2204 | -from charmhelpers.core.hookenv import ( |
2205 | - log, |
2206 | - DEBUG, |
2207 | - INFO, |
2208 | - WARNING, |
2209 | - ERROR, |
2210 | -) |
2211 | -from charmhelpers.core import unitdata |
2212 | -from charmhelpers.core.host import file_hash |
2213 | -from charmhelpers.contrib.hardening.audits import BaseAudit |
2214 | -from charmhelpers.contrib.hardening.templating import ( |
2215 | - get_template_path, |
2216 | - render_and_write, |
2217 | -) |
2218 | -from charmhelpers.contrib.hardening import utils |
2219 | - |
2220 | - |
2221 | -class BaseFileAudit(BaseAudit): |
2222 | - """Base class for file audits. |
2223 | - |
2224 | - Provides api stubs for compliance check flow that must be used by any class |
2225 | - that implemented this one. |
2226 | - """ |
2227 | - |
2228 | - def __init__(self, paths, always_comply=False, *args, **kwargs): |
2229 | - """ |
2230 | - :param paths: string path of list of paths of files we want to apply |
2231 | - compliance checks are criteria to. |
2232 | - :param always_comply: if true compliance criteria is always applied |
2233 | - else compliance is skipped for non-existent |
2234 | - paths. |
2235 | - """ |
2236 | - super(BaseFileAudit, self).__init__(*args, **kwargs) |
2237 | - self.always_comply = always_comply |
2238 | - if isinstance(paths, string_types) or not hasattr(paths, '__iter__'): |
2239 | - self.paths = [paths] |
2240 | - else: |
2241 | - self.paths = paths |
2242 | - |
2243 | - def ensure_compliance(self): |
2244 | - """Ensure that the all registered files comply to registered criteria. |
2245 | - """ |
2246 | - for p in self.paths: |
2247 | - if os.path.exists(p): |
2248 | - if self.is_compliant(p): |
2249 | - continue |
2250 | - |
2251 | - log('File %s is not in compliance.' % p, level=INFO) |
2252 | - else: |
2253 | - if not self.always_comply: |
2254 | - log("Non-existent path '%s' - skipping compliance check" |
2255 | - % (p), level=INFO) |
2256 | - continue |
2257 | - |
2258 | - if self._take_action(): |
2259 | - log("Applying compliance criteria to '%s'" % (p), level=INFO) |
2260 | - self.comply(p) |
2261 | - |
2262 | - def is_compliant(self, path): |
2263 | - """Audits the path to see if it is compliance. |
2264 | - |
2265 | - :param path: the path to the file that should be checked. |
2266 | - """ |
2267 | - raise NotImplementedError |
2268 | - |
2269 | - def comply(self, path): |
2270 | - """Enforces the compliance of a path. |
2271 | - |
2272 | - :param path: the path to the file that should be enforced. |
2273 | - """ |
2274 | - raise NotImplementedError |
2275 | - |
2276 | - @classmethod |
2277 | - def _get_stat(cls, path): |
2278 | - """Returns the Posix st_stat information for the specified file path. |
2279 | - |
2280 | - :param path: the path to get the st_stat information for. |
2281 | - :returns: an st_stat object for the path or None if the path doesn't |
2282 | - exist. |
2283 | - """ |
2284 | - return os.stat(path) |
2285 | - |
2286 | - |
2287 | -class FilePermissionAudit(BaseFileAudit): |
2288 | - """Implements an audit for file permissions and ownership for a user. |
2289 | - |
2290 | - This class implements functionality that ensures that a specific user/group |
2291 | - will own the file(s) specified and that the permissions specified are |
2292 | - applied properly to the file. |
2293 | - """ |
2294 | - def __init__(self, paths, user, group=None, mode=0o600, **kwargs): |
2295 | - self.user = user |
2296 | - self.group = group |
2297 | - self.mode = mode |
2298 | - super(FilePermissionAudit, self).__init__(paths, user, group, mode, |
2299 | - **kwargs) |
2300 | - |
2301 | - @property |
2302 | - def user(self): |
2303 | - return self._user |
2304 | - |
2305 | - @user.setter |
2306 | - def user(self, name): |
2307 | - try: |
2308 | - user = pwd.getpwnam(name) |
2309 | - except KeyError: |
2310 | - log('Unknown user %s' % name, level=ERROR) |
2311 | - user = None |
2312 | - self._user = user |
2313 | - |
2314 | - @property |
2315 | - def group(self): |
2316 | - return self._group |
2317 | - |
2318 | - @group.setter |
2319 | - def group(self, name): |
2320 | - try: |
2321 | - group = None |
2322 | - if name: |
2323 | - group = grp.getgrnam(name) |
2324 | - else: |
2325 | - group = grp.getgrgid(self.user.pw_gid) |
2326 | - except KeyError: |
2327 | - log('Unknown group %s' % name, level=ERROR) |
2328 | - self._group = group |
2329 | - |
2330 | - def is_compliant(self, path): |
2331 | - """Checks if the path is in compliance. |
2332 | - |
2333 | - Used to determine if the path specified meets the necessary |
2334 | - requirements to be in compliance with the check itself. |
2335 | - |
2336 | - :param path: the file path to check |
2337 | - :returns: True if the path is compliant, False otherwise. |
2338 | - """ |
2339 | - stat = self._get_stat(path) |
2340 | - user = self.user |
2341 | - group = self.group |
2342 | - |
2343 | - compliant = True |
2344 | - if stat.st_uid != user.pw_uid or stat.st_gid != group.gr_gid: |
2345 | - log('File %s is not owned by %s:%s.' % (path, user.pw_name, |
2346 | - group.gr_name), |
2347 | - level=INFO) |
2348 | - compliant = False |
2349 | - |
2350 | - # POSIX refers to the st_mode bits as corresponding to both the |
2351 | - # file type and file permission bits, where the least significant 12 |
2352 | - # bits (o7777) are the suid (11), sgid (10), sticky bits (9), and the |
2353 | - # file permission bits (8-0) |
2354 | - perms = stat.st_mode & 0o7777 |
2355 | - if perms != self.mode: |
2356 | - log('File %s has incorrect permissions, currently set to %s' % |
2357 | - (path, oct(stat.st_mode & 0o7777)), level=INFO) |
2358 | - compliant = False |
2359 | - |
2360 | - return compliant |
2361 | - |
2362 | - def comply(self, path): |
2363 | - """Issues a chown and chmod to the file paths specified.""" |
2364 | - utils.ensure_permissions(path, self.user.pw_name, self.group.gr_name, |
2365 | - self.mode) |
2366 | - |
2367 | - |
2368 | -class DirectoryPermissionAudit(FilePermissionAudit): |
2369 | - """Performs a permission check for the specified directory path.""" |
2370 | - |
2371 | - def __init__(self, paths, user, group=None, mode=0o600, |
2372 | - recursive=True, **kwargs): |
2373 | - super(DirectoryPermissionAudit, self).__init__(paths, user, group, |
2374 | - mode, **kwargs) |
2375 | - self.recursive = recursive |
2376 | - |
2377 | - def is_compliant(self, path): |
2378 | - """Checks if the directory is compliant. |
2379 | - |
2380 | - Used to determine if the path specified and all of its children |
2381 | - directories are in compliance with the check itself. |
2382 | - |
2383 | - :param path: the directory path to check |
2384 | - :returns: True if the directory tree is compliant, otherwise False. |
2385 | - """ |
2386 | - if not os.path.isdir(path): |
2387 | - log('Path specified %s is not a directory.' % path, level=ERROR) |
2388 | - raise ValueError("%s is not a directory." % path) |
2389 | - |
2390 | - if not self.recursive: |
2391 | - return super(DirectoryPermissionAudit, self).is_compliant(path) |
2392 | - |
2393 | - compliant = True |
2394 | - for root, dirs, _ in os.walk(path): |
2395 | - if len(dirs) > 0: |
2396 | - continue |
2397 | - |
2398 | - if not super(DirectoryPermissionAudit, self).is_compliant(root): |
2399 | - compliant = False |
2400 | - continue |
2401 | - |
2402 | - return compliant |
2403 | - |
2404 | - def comply(self, path): |
2405 | - for root, dirs, _ in os.walk(path): |
2406 | - if len(dirs) > 0: |
2407 | - super(DirectoryPermissionAudit, self).comply(root) |
2408 | - |
2409 | - |
2410 | -class ReadOnly(BaseFileAudit): |
2411 | - """Audits that files and folders are read only.""" |
2412 | - def __init__(self, paths, *args, **kwargs): |
2413 | - super(ReadOnly, self).__init__(paths=paths, *args, **kwargs) |
2414 | - |
2415 | - def is_compliant(self, path): |
2416 | - try: |
2417 | - output = check_output(['find', path, '-perm', '-go+w', |
2418 | - '-type', 'f']).strip() |
2419 | - |
2420 | - # The find above will find any files which have permission sets |
2421 | - # which allow too broad of write access. As such, the path is |
2422 | - # compliant if there is no output. |
2423 | - if output: |
2424 | - return False |
2425 | - |
2426 | - return True |
2427 | - except CalledProcessError as e: |
2428 | - log('Error occurred checking finding writable files for %s. ' |
2429 | - 'Error information is: command %s failed with returncode ' |
2430 | - '%d and output %s.\n%s' % (path, e.cmd, e.returncode, e.output, |
2431 | - format_exc(e)), level=ERROR) |
2432 | - return False |
2433 | - |
2434 | - def comply(self, path): |
2435 | - try: |
2436 | - check_output(['chmod', 'go-w', '-R', path]) |
2437 | - except CalledProcessError as e: |
2438 | - log('Error occurred removing writeable permissions for %s. ' |
2439 | - 'Error information is: command %s failed with returncode ' |
2440 | - '%d and output %s.\n%s' % (path, e.cmd, e.returncode, e.output, |
2441 | - format_exc(e)), level=ERROR) |
2442 | - |
2443 | - |
2444 | -class NoReadWriteForOther(BaseFileAudit): |
2445 | - """Ensures that the files found under the base path are readable or |
2446 | - writable by anyone other than the owner or the group. |
2447 | - """ |
2448 | - def __init__(self, paths): |
2449 | - super(NoReadWriteForOther, self).__init__(paths) |
2450 | - |
2451 | - def is_compliant(self, path): |
2452 | - try: |
2453 | - cmd = ['find', path, '-perm', '-o+r', '-type', 'f', '-o', |
2454 | - '-perm', '-o+w', '-type', 'f'] |
2455 | - output = check_output(cmd).strip() |
2456 | - |
2457 | - # The find above here will find any files which have read or |
2458 | - # write permissions for other, meaning there is too broad of access |
2459 | - # to read/write the file. As such, the path is compliant if there's |
2460 | - # no output. |
2461 | - if output: |
2462 | - return False |
2463 | - |
2464 | - return True |
2465 | - except CalledProcessError as e: |
2466 | - log('Error occurred while finding files which are readable or ' |
2467 | - 'writable to the world in %s. ' |
2468 | - 'Command output is: %s.' % (path, e.output), level=ERROR) |
2469 | - |
2470 | - def comply(self, path): |
2471 | - try: |
2472 | - check_output(['chmod', '-R', 'o-rw', path]) |
2473 | - except CalledProcessError as e: |
2474 | - log('Error occurred attempting to change modes of files under ' |
2475 | - 'path %s. Output of command is: %s' % (path, e.output)) |
2476 | - |
2477 | - |
2478 | -class NoSUIDSGIDAudit(BaseFileAudit): |
2479 | - """Audits that specified files do not have SUID/SGID bits set.""" |
2480 | - def __init__(self, paths, *args, **kwargs): |
2481 | - super(NoSUIDSGIDAudit, self).__init__(paths=paths, *args, **kwargs) |
2482 | - |
2483 | - def is_compliant(self, path): |
2484 | - stat = self._get_stat(path) |
2485 | - if (stat.st_mode & (S_ISGID | S_ISUID)) != 0: |
2486 | - return False |
2487 | - |
2488 | - return True |
2489 | - |
2490 | - def comply(self, path): |
2491 | - try: |
2492 | - log('Removing suid/sgid from %s.' % path, level=DEBUG) |
2493 | - check_output(['chmod', '-s', path]) |
2494 | - except CalledProcessError as e: |
2495 | - log('Error occurred removing suid/sgid from %s.' |
2496 | - 'Error information is: command %s failed with returncode ' |
2497 | - '%d and output %s.\n%s' % (path, e.cmd, e.returncode, e.output, |
2498 | - format_exc(e)), level=ERROR) |
2499 | - |
2500 | - |
2501 | -class TemplatedFile(BaseFileAudit): |
2502 | - """The TemplatedFileAudit audits the contents of a templated file. |
2503 | - |
2504 | - This audit renders a file from a template, sets the appropriate file |
2505 | - permissions, then generates a hashsum with which to check the content |
2506 | - changed. |
2507 | - """ |
2508 | - def __init__(self, path, context, template_dir, mode, user='root', |
2509 | - group='root', service_actions=None, **kwargs): |
2510 | - self.context = context |
2511 | - self.user = user |
2512 | - self.group = group |
2513 | - self.mode = mode |
2514 | - self.template_dir = template_dir |
2515 | - self.service_actions = service_actions |
2516 | - super(TemplatedFile, self).__init__(paths=path, always_comply=True, |
2517 | - **kwargs) |
2518 | - |
2519 | - def is_compliant(self, path): |
2520 | - """Determines if the templated file is compliant. |
2521 | - |
2522 | - A templated file is only compliant if it has not changed (as |
2523 | - determined by its sha256 hashsum) AND its file permissions are set |
2524 | - appropriately. |
2525 | - |
2526 | - :param path: the path to check compliance. |
2527 | - """ |
2528 | - same_templates = self.templates_match(path) |
2529 | - same_content = self.contents_match(path) |
2530 | - same_permissions = self.permissions_match(path) |
2531 | - |
2532 | - if same_content and same_permissions and same_templates: |
2533 | - return True |
2534 | - |
2535 | - return False |
2536 | - |
2537 | - def run_service_actions(self): |
2538 | - """Run any actions on services requested.""" |
2539 | - if not self.service_actions: |
2540 | - return |
2541 | - |
2542 | - for svc_action in self.service_actions: |
2543 | - name = svc_action['service'] |
2544 | - actions = svc_action['actions'] |
2545 | - log("Running service '%s' actions '%s'" % (name, actions), |
2546 | - level=DEBUG) |
2547 | - for action in actions: |
2548 | - cmd = ['service', name, action] |
2549 | - try: |
2550 | - check_call(cmd) |
2551 | - except CalledProcessError as exc: |
2552 | - log("Service name='%s' action='%s' failed - %s" % |
2553 | - (name, action, exc), level=WARNING) |
2554 | - |
2555 | - def comply(self, path): |
2556 | - """Ensures the contents and the permissions of the file. |
2557 | - |
2558 | - :param path: the path to correct |
2559 | - """ |
2560 | - dirname = os.path.dirname(path) |
2561 | - if not os.path.exists(dirname): |
2562 | - os.makedirs(dirname) |
2563 | - |
2564 | - self.pre_write() |
2565 | - render_and_write(self.template_dir, path, self.context()) |
2566 | - utils.ensure_permissions(path, self.user, self.group, self.mode) |
2567 | - self.run_service_actions() |
2568 | - self.save_checksum(path) |
2569 | - self.post_write() |
2570 | - |
2571 | - def pre_write(self): |
2572 | - """Invoked prior to writing the template.""" |
2573 | - pass |
2574 | - |
2575 | - def post_write(self): |
2576 | - """Invoked after writing the template.""" |
2577 | - pass |
2578 | - |
2579 | - def templates_match(self, path): |
2580 | - """Determines if the template files are the same. |
2581 | - |
2582 | - The template file equality is determined by the hashsum of the |
2583 | - template files themselves. If there is no hashsum, then the content |
2584 | - cannot be sure to be the same so treat it as if they changed. |
2585 | - Otherwise, return whether or not the hashsums are the same. |
2586 | - |
2587 | - :param path: the path to check |
2588 | - :returns: boolean |
2589 | - """ |
2590 | - template_path = get_template_path(self.template_dir, path) |
2591 | - key = 'hardening:template:%s' % template_path |
2592 | - template_checksum = file_hash(template_path) |
2593 | - kv = unitdata.kv() |
2594 | - stored_tmplt_checksum = kv.get(key) |
2595 | - if not stored_tmplt_checksum: |
2596 | - kv.set(key, template_checksum) |
2597 | - kv.flush() |
2598 | - log('Saved template checksum for %s.' % template_path, |
2599 | - level=DEBUG) |
2600 | - # Since we don't have a template checksum, then assume it doesn't |
2601 | - # match and return that the template is different. |
2602 | - return False |
2603 | - elif stored_tmplt_checksum != template_checksum: |
2604 | - kv.set(key, template_checksum) |
2605 | - kv.flush() |
2606 | - log('Updated template checksum for %s.' % template_path, |
2607 | - level=DEBUG) |
2608 | - return False |
2609 | - |
2610 | - # Here the template hasn't changed based upon the calculated |
2611 | - # checksum of the template and what was previously stored. |
2612 | - return True |
2613 | - |
2614 | - def contents_match(self, path): |
2615 | - """Determines if the file content is the same. |
2616 | - |
2617 | - This is determined by comparing hashsum of the file contents and |
2618 | - the saved hashsum. If there is no hashsum, then the content cannot |
2619 | - be sure to be the same so treat them as if they are not the same. |
2620 | - Otherwise, return True if the hashsums are the same, False if they |
2621 | - are not the same. |
2622 | - |
2623 | - :param path: the file to check. |
2624 | - """ |
2625 | - checksum = file_hash(path) |
2626 | - |
2627 | - kv = unitdata.kv() |
2628 | - stored_checksum = kv.get('hardening:%s' % path) |
2629 | - if not stored_checksum: |
2630 | - # If the checksum hasn't been generated, return False to ensure |
2631 | - # the file is written and the checksum stored. |
2632 | - log('Checksum for %s has not been calculated.' % path, level=DEBUG) |
2633 | - return False |
2634 | - elif stored_checksum != checksum: |
2635 | - log('Checksum mismatch for %s.' % path, level=DEBUG) |
2636 | - return False |
2637 | - |
2638 | - return True |
2639 | - |
2640 | - def permissions_match(self, path): |
2641 | - """Determines if the file owner and permissions match. |
2642 | - |
2643 | - :param path: the path to check. |
2644 | - """ |
2645 | - audit = FilePermissionAudit(path, self.user, self.group, self.mode) |
2646 | - return audit.is_compliant(path) |
2647 | - |
2648 | - def save_checksum(self, path): |
2649 | - """Calculates and saves the checksum for the path specified. |
2650 | - |
2651 | - :param path: the path of the file to save the checksum. |
2652 | - """ |
2653 | - checksum = file_hash(path) |
2654 | - kv = unitdata.kv() |
2655 | - kv.set('hardening:%s' % path, checksum) |
2656 | - kv.flush() |
2657 | - |
2658 | - |
2659 | -class DeletedFile(BaseFileAudit): |
2660 | - """Audit to ensure that a file is deleted.""" |
2661 | - def __init__(self, paths): |
2662 | - super(DeletedFile, self).__init__(paths) |
2663 | - |
2664 | - def is_compliant(self, path): |
2665 | - return not os.path.exists(path) |
2666 | - |
2667 | - def comply(self, path): |
2668 | - os.remove(path) |
2669 | - |
2670 | - |
2671 | -class FileContentAudit(BaseFileAudit): |
2672 | - """Audit the contents of a file.""" |
2673 | - def __init__(self, paths, cases, **kwargs): |
2674 | - # Cases we expect to pass |
2675 | - self.pass_cases = cases.get('pass', []) |
2676 | - # Cases we expect to fail |
2677 | - self.fail_cases = cases.get('fail', []) |
2678 | - super(FileContentAudit, self).__init__(paths, **kwargs) |
2679 | - |
2680 | - def is_compliant(self, path): |
2681 | - """ |
2682 | - Given a set of content matching cases i.e. tuple(regex, bool) where |
2683 | - bool value denotes whether or not regex is expected to match, check that |
2684 | - all cases match as expected with the contents of the file. Cases can be |
2685 | - expected to pass of fail. |
2686 | - |
2687 | - :param path: Path of file to check. |
2688 | - :returns: Boolean value representing whether or not all cases are |
2689 | - found to be compliant. |
2690 | - """ |
2691 | - log("Auditing contents of file '%s'" % (path), level=DEBUG) |
2692 | - with open(path, 'r') as fd: |
2693 | - contents = fd.read() |
2694 | - |
2695 | - matches = 0 |
2696 | - for pattern in self.pass_cases: |
2697 | - key = re.compile(pattern, flags=re.MULTILINE) |
2698 | - results = re.search(key, contents) |
2699 | - if results: |
2700 | - matches += 1 |
2701 | - else: |
2702 | - log("Pattern '%s' was expected to pass but instead it failed" |
2703 | - % (pattern), level=WARNING) |
2704 | - |
2705 | - for pattern in self.fail_cases: |
2706 | - key = re.compile(pattern, flags=re.MULTILINE) |
2707 | - results = re.search(key, contents) |
2708 | - if not results: |
2709 | - matches += 1 |
2710 | - else: |
2711 | - log("Pattern '%s' was expected to fail but instead it passed" |
2712 | - % (pattern), level=WARNING) |
2713 | - |
2714 | - total = len(self.pass_cases) + len(self.fail_cases) |
2715 | - log("Checked %s cases and %s passed" % (total, matches), level=DEBUG) |
2716 | - return matches == total |
2717 | - |
2718 | - def comply(self, *args, **kwargs): |
2719 | - """NOOP since we just issue warnings. This is to avoid the |
2720 | - NotImplememtedError. |
2721 | - """ |
2722 | - log("Not applying any compliance criteria, only checks.", level=INFO) |
2723 | |
2724 | === removed file 'hooks/charmhelpers/contrib/hardening/harden.py' |
2725 | --- hooks/charmhelpers/contrib/hardening/harden.py 2016-04-22 04:35:32 +0000 |
2726 | +++ hooks/charmhelpers/contrib/hardening/harden.py 1970-01-01 00:00:00 +0000 |
2727 | @@ -1,84 +0,0 @@ |
2728 | -# Copyright 2016 Canonical Limited. |
2729 | -# |
2730 | -# This file is part of charm-helpers. |
2731 | -# |
2732 | -# charm-helpers is free software: you can redistribute it and/or modify |
2733 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2734 | -# published by the Free Software Foundation. |
2735 | -# |
2736 | -# charm-helpers is distributed in the hope that it will be useful, |
2737 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2738 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2739 | -# GNU Lesser General Public License for more details. |
2740 | -# |
2741 | -# You should have received a copy of the GNU Lesser General Public License |
2742 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2743 | - |
2744 | -import six |
2745 | - |
2746 | -from collections import OrderedDict |
2747 | - |
2748 | -from charmhelpers.core.hookenv import ( |
2749 | - config, |
2750 | - log, |
2751 | - DEBUG, |
2752 | - WARNING, |
2753 | -) |
2754 | -from charmhelpers.contrib.hardening.host.checks import run_os_checks |
2755 | -from charmhelpers.contrib.hardening.ssh.checks import run_ssh_checks |
2756 | -from charmhelpers.contrib.hardening.mysql.checks import run_mysql_checks |
2757 | -from charmhelpers.contrib.hardening.apache.checks import run_apache_checks |
2758 | - |
2759 | - |
2760 | -def harden(overrides=None): |
2761 | - """Hardening decorator. |
2762 | - |
2763 | - This is the main entry point for running the hardening stack. In order to |
2764 | - run modules of the stack you must add this decorator to charm hook(s) and |
2765 | - ensure that your charm config.yaml contains the 'harden' option set to |
2766 | - one or more of the supported modules. Setting these will cause the |
2767 | - corresponding hardening code to be run when the hook fires. |
2768 | - |
2769 | - This decorator can and should be applied to more than one hook or function |
2770 | - such that hardening modules are called multiple times. This is because |
2771 | - subsequent calls will perform auditing checks that will report any changes |
2772 | - to resources hardened by the first run (and possibly perform compliance |
2773 | - actions as a result of any detected infractions). |
2774 | - |
2775 | - :param overrides: Optional list of stack modules used to override those |
2776 | - provided with 'harden' config. |
2777 | - :returns: Returns value returned by decorated function once executed. |
2778 | - """ |
2779 | - def _harden_inner1(f): |
2780 | - log("Hardening function '%s'" % (f.__name__), level=DEBUG) |
2781 | - |
2782 | - def _harden_inner2(*args, **kwargs): |
2783 | - RUN_CATALOG = OrderedDict([('os', run_os_checks), |
2784 | - ('ssh', run_ssh_checks), |
2785 | - ('mysql', run_mysql_checks), |
2786 | - ('apache', run_apache_checks)]) |
2787 | - |
2788 | - enabled = overrides or (config("harden") or "").split() |
2789 | - if enabled: |
2790 | - modules_to_run = [] |
2791 | - # modules will always be performed in the following order |
2792 | - for module, func in six.iteritems(RUN_CATALOG): |
2793 | - if module in enabled: |
2794 | - enabled.remove(module) |
2795 | - modules_to_run.append(func) |
2796 | - |
2797 | - if enabled: |
2798 | - log("Unknown hardening modules '%s' - ignoring" % |
2799 | - (', '.join(enabled)), level=WARNING) |
2800 | - |
2801 | - for hardener in modules_to_run: |
2802 | - log("Executing hardening module '%s'" % |
2803 | - (hardener.__name__), level=DEBUG) |
2804 | - hardener() |
2805 | - else: |
2806 | - log("No hardening applied to '%s'" % (f.__name__), level=DEBUG) |
2807 | - |
2808 | - return f(*args, **kwargs) |
2809 | - return _harden_inner2 |
2810 | - |
2811 | - return _harden_inner1 |
2812 | |
2813 | === removed directory 'hooks/charmhelpers/contrib/hardening/host' |
2814 | === removed file 'hooks/charmhelpers/contrib/hardening/host/__init__.py' |
2815 | --- hooks/charmhelpers/contrib/hardening/host/__init__.py 2016-04-22 04:35:32 +0000 |
2816 | +++ hooks/charmhelpers/contrib/hardening/host/__init__.py 1970-01-01 00:00:00 +0000 |
2817 | @@ -1,19 +0,0 @@ |
2818 | -# Copyright 2016 Canonical Limited. |
2819 | -# |
2820 | -# This file is part of charm-helpers. |
2821 | -# |
2822 | -# charm-helpers is free software: you can redistribute it and/or modify |
2823 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2824 | -# published by the Free Software Foundation. |
2825 | -# |
2826 | -# charm-helpers is distributed in the hope that it will be useful, |
2827 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2828 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2829 | -# GNU Lesser General Public License for more details. |
2830 | -# |
2831 | -# You should have received a copy of the GNU Lesser General Public License |
2832 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2833 | - |
2834 | -from os import path |
2835 | - |
2836 | -TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') |
2837 | |
2838 | === removed directory 'hooks/charmhelpers/contrib/hardening/host/checks' |
2839 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/__init__.py' |
2840 | --- hooks/charmhelpers/contrib/hardening/host/checks/__init__.py 2016-04-22 04:35:32 +0000 |
2841 | +++ hooks/charmhelpers/contrib/hardening/host/checks/__init__.py 1970-01-01 00:00:00 +0000 |
2842 | @@ -1,50 +0,0 @@ |
2843 | -# Copyright 2016 Canonical Limited. |
2844 | -# |
2845 | -# This file is part of charm-helpers. |
2846 | -# |
2847 | -# charm-helpers is free software: you can redistribute it and/or modify |
2848 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2849 | -# published by the Free Software Foundation. |
2850 | -# |
2851 | -# charm-helpers is distributed in the hope that it will be useful, |
2852 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2853 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2854 | -# GNU Lesser General Public License for more details. |
2855 | -# |
2856 | -# You should have received a copy of the GNU Lesser General Public License |
2857 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2858 | - |
2859 | -from charmhelpers.core.hookenv import ( |
2860 | - log, |
2861 | - DEBUG, |
2862 | -) |
2863 | -from charmhelpers.contrib.hardening.host.checks import ( |
2864 | - apt, |
2865 | - limits, |
2866 | - login, |
2867 | - minimize_access, |
2868 | - pam, |
2869 | - profile, |
2870 | - securetty, |
2871 | - suid_sgid, |
2872 | - sysctl |
2873 | -) |
2874 | - |
2875 | - |
2876 | -def run_os_checks(): |
2877 | - log("Starting OS hardening checks.", level=DEBUG) |
2878 | - checks = apt.get_audits() |
2879 | - checks.extend(limits.get_audits()) |
2880 | - checks.extend(login.get_audits()) |
2881 | - checks.extend(minimize_access.get_audits()) |
2882 | - checks.extend(pam.get_audits()) |
2883 | - checks.extend(profile.get_audits()) |
2884 | - checks.extend(securetty.get_audits()) |
2885 | - checks.extend(suid_sgid.get_audits()) |
2886 | - checks.extend(sysctl.get_audits()) |
2887 | - |
2888 | - for check in checks: |
2889 | - log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) |
2890 | - check.ensure_compliance() |
2891 | - |
2892 | - log("OS hardening checks complete.", level=DEBUG) |
2893 | |
2894 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/apt.py' |
2895 | --- hooks/charmhelpers/contrib/hardening/host/checks/apt.py 2016-04-22 04:35:32 +0000 |
2896 | +++ hooks/charmhelpers/contrib/hardening/host/checks/apt.py 1970-01-01 00:00:00 +0000 |
2897 | @@ -1,39 +0,0 @@ |
2898 | -# Copyright 2016 Canonical Limited. |
2899 | -# |
2900 | -# This file is part of charm-helpers. |
2901 | -# |
2902 | -# charm-helpers is free software: you can redistribute it and/or modify |
2903 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2904 | -# published by the Free Software Foundation. |
2905 | -# |
2906 | -# charm-helpers is distributed in the hope that it will be useful, |
2907 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2908 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2909 | -# GNU Lesser General Public License for more details. |
2910 | -# |
2911 | -# You should have received a copy of the GNU Lesser General Public License |
2912 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2913 | - |
2914 | -from charmhelpers.contrib.hardening.utils import get_settings |
2915 | -from charmhelpers.contrib.hardening.audits.apt import ( |
2916 | - AptConfig, |
2917 | - RestrictedPackages, |
2918 | -) |
2919 | - |
2920 | - |
2921 | -def get_audits(): |
2922 | - """Get OS hardening apt audits. |
2923 | - |
2924 | - :returns: dictionary of audits |
2925 | - """ |
2926 | - audits = [AptConfig([{'key': 'APT::Get::AllowUnauthenticated', |
2927 | - 'expected': 'false'}])] |
2928 | - |
2929 | - settings = get_settings('os') |
2930 | - clean_packages = settings['security']['packages_clean'] |
2931 | - if clean_packages: |
2932 | - security_packages = settings['security']['packages_list'] |
2933 | - if security_packages: |
2934 | - audits.append(RestrictedPackages(security_packages)) |
2935 | - |
2936 | - return audits |
2937 | |
2938 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/limits.py' |
2939 | --- hooks/charmhelpers/contrib/hardening/host/checks/limits.py 2016-04-22 04:35:32 +0000 |
2940 | +++ hooks/charmhelpers/contrib/hardening/host/checks/limits.py 1970-01-01 00:00:00 +0000 |
2941 | @@ -1,55 +0,0 @@ |
2942 | -# Copyright 2016 Canonical Limited. |
2943 | -# |
2944 | -# This file is part of charm-helpers. |
2945 | -# |
2946 | -# charm-helpers is free software: you can redistribute it and/or modify |
2947 | -# it under the terms of the GNU Lesser General Public License version 3 as |
2948 | -# published by the Free Software Foundation. |
2949 | -# |
2950 | -# charm-helpers is distributed in the hope that it will be useful, |
2951 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2952 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2953 | -# GNU Lesser General Public License for more details. |
2954 | -# |
2955 | -# You should have received a copy of the GNU Lesser General Public License |
2956 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
2957 | - |
2958 | -from charmhelpers.contrib.hardening.audits.file import ( |
2959 | - DirectoryPermissionAudit, |
2960 | - TemplatedFile, |
2961 | -) |
2962 | -from charmhelpers.contrib.hardening.host import TEMPLATES_DIR |
2963 | -from charmhelpers.contrib.hardening import utils |
2964 | - |
2965 | - |
2966 | -def get_audits(): |
2967 | - """Get OS hardening security limits audits. |
2968 | - |
2969 | - :returns: dictionary of audits |
2970 | - """ |
2971 | - audits = [] |
2972 | - settings = utils.get_settings('os') |
2973 | - |
2974 | - # Ensure that the /etc/security/limits.d directory is only writable |
2975 | - # by the root user, but others can execute and read. |
2976 | - audits.append(DirectoryPermissionAudit('/etc/security/limits.d', |
2977 | - user='root', group='root', |
2978 | - mode=0o755)) |
2979 | - |
2980 | - # If core dumps are not enabled, then don't allow core dumps to be |
2981 | - # created as they may contain sensitive information. |
2982 | - if not settings['security']['kernel_enable_core_dump']: |
2983 | - audits.append(TemplatedFile('/etc/security/limits.d/10.hardcore.conf', |
2984 | - SecurityLimitsContext(), |
2985 | - template_dir=TEMPLATES_DIR, |
2986 | - user='root', group='root', mode=0o0440)) |
2987 | - return audits |
2988 | - |
2989 | - |
2990 | -class SecurityLimitsContext(object): |
2991 | - |
2992 | - def __call__(self): |
2993 | - settings = utils.get_settings('os') |
2994 | - ctxt = {'disable_core_dump': |
2995 | - not settings['security']['kernel_enable_core_dump']} |
2996 | - return ctxt |
2997 | |
2998 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/login.py' |
2999 | --- hooks/charmhelpers/contrib/hardening/host/checks/login.py 2016-04-22 04:35:32 +0000 |
3000 | +++ hooks/charmhelpers/contrib/hardening/host/checks/login.py 1970-01-01 00:00:00 +0000 |
3001 | @@ -1,67 +0,0 @@ |
3002 | -# Copyright 2016 Canonical Limited. |
3003 | -# |
3004 | -# This file is part of charm-helpers. |
3005 | -# |
3006 | -# charm-helpers is free software: you can redistribute it and/or modify |
3007 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3008 | -# published by the Free Software Foundation. |
3009 | -# |
3010 | -# charm-helpers is distributed in the hope that it will be useful, |
3011 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3012 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3013 | -# GNU Lesser General Public License for more details. |
3014 | -# |
3015 | -# You should have received a copy of the GNU Lesser General Public License |
3016 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3017 | - |
3018 | -from six import string_types |
3019 | - |
3020 | -from charmhelpers.contrib.hardening.audits.file import TemplatedFile |
3021 | -from charmhelpers.contrib.hardening.host import TEMPLATES_DIR |
3022 | -from charmhelpers.contrib.hardening import utils |
3023 | - |
3024 | - |
3025 | -def get_audits(): |
3026 | - """Get OS hardening login.defs audits. |
3027 | - |
3028 | - :returns: dictionary of audits |
3029 | - """ |
3030 | - audits = [TemplatedFile('/etc/login.defs', LoginContext(), |
3031 | - template_dir=TEMPLATES_DIR, |
3032 | - user='root', group='root', mode=0o0444)] |
3033 | - return audits |
3034 | - |
3035 | - |
3036 | -class LoginContext(object): |
3037 | - |
3038 | - def __call__(self): |
3039 | - settings = utils.get_settings('os') |
3040 | - |
3041 | - # Octal numbers in yaml end up being turned into decimal, |
3042 | - # so check if the umask is entered as a string (e.g. '027') |
3043 | - # or as an octal umask as we know it (e.g. 002). If its not |
3044 | - # a string assume it to be octal and turn it into an octal |
3045 | - # string. |
3046 | - umask = settings['environment']['umask'] |
3047 | - if not isinstance(umask, string_types): |
3048 | - umask = '%s' % oct(umask) |
3049 | - |
3050 | - ctxt = { |
3051 | - 'additional_user_paths': |
3052 | - settings['environment']['extra_user_paths'], |
3053 | - 'umask': umask, |
3054 | - 'pwd_max_age': settings['auth']['pw_max_age'], |
3055 | - 'pwd_min_age': settings['auth']['pw_min_age'], |
3056 | - 'uid_min': settings['auth']['uid_min'], |
3057 | - 'sys_uid_min': settings['auth']['sys_uid_min'], |
3058 | - 'sys_uid_max': settings['auth']['sys_uid_max'], |
3059 | - 'gid_min': settings['auth']['gid_min'], |
3060 | - 'sys_gid_min': settings['auth']['sys_gid_min'], |
3061 | - 'sys_gid_max': settings['auth']['sys_gid_max'], |
3062 | - 'login_retries': settings['auth']['retries'], |
3063 | - 'login_timeout': settings['auth']['timeout'], |
3064 | - 'chfn_restrict': settings['auth']['chfn_restrict'], |
3065 | - 'allow_login_without_home': settings['auth']['allow_homeless'] |
3066 | - } |
3067 | - |
3068 | - return ctxt |
3069 | |
3070 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py' |
3071 | --- hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py 2016-04-22 04:35:32 +0000 |
3072 | +++ hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py 1970-01-01 00:00:00 +0000 |
3073 | @@ -1,52 +0,0 @@ |
3074 | -# Copyright 2016 Canonical Limited. |
3075 | -# |
3076 | -# This file is part of charm-helpers. |
3077 | -# |
3078 | -# charm-helpers is free software: you can redistribute it and/or modify |
3079 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3080 | -# published by the Free Software Foundation. |
3081 | -# |
3082 | -# charm-helpers is distributed in the hope that it will be useful, |
3083 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3084 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3085 | -# GNU Lesser General Public License for more details. |
3086 | -# |
3087 | -# You should have received a copy of the GNU Lesser General Public License |
3088 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3089 | - |
3090 | -from charmhelpers.contrib.hardening.audits.file import ( |
3091 | - FilePermissionAudit, |
3092 | - ReadOnly, |
3093 | -) |
3094 | -from charmhelpers.contrib.hardening import utils |
3095 | - |
3096 | - |
3097 | -def get_audits(): |
3098 | - """Get OS hardening access audits. |
3099 | - |
3100 | - :returns: dictionary of audits |
3101 | - """ |
3102 | - audits = [] |
3103 | - settings = utils.get_settings('os') |
3104 | - |
3105 | - # Remove write permissions from $PATH folders for all regular users. |
3106 | - # This prevents changing system-wide commands from normal users. |
3107 | - path_folders = {'/usr/local/sbin', |
3108 | - '/usr/local/bin', |
3109 | - '/usr/sbin', |
3110 | - '/usr/bin', |
3111 | - '/bin'} |
3112 | - extra_user_paths = settings['environment']['extra_user_paths'] |
3113 | - path_folders.update(extra_user_paths) |
3114 | - audits.append(ReadOnly(path_folders)) |
3115 | - |
3116 | - # Only allow the root user to have access to the shadow file. |
3117 | - audits.append(FilePermissionAudit('/etc/shadow', 'root', 'root', 0o0600)) |
3118 | - |
3119 | - if 'change_user' not in settings['security']['users_allow']: |
3120 | - # su should only be accessible to user and group root, unless it is |
3121 | - # expressly defined to allow users to change to root via the |
3122 | - # security_users_allow config option. |
3123 | - audits.append(FilePermissionAudit('/bin/su', 'root', 'root', 0o750)) |
3124 | - |
3125 | - return audits |
3126 | |
3127 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/pam.py' |
3128 | --- hooks/charmhelpers/contrib/hardening/host/checks/pam.py 2016-04-22 04:35:32 +0000 |
3129 | +++ hooks/charmhelpers/contrib/hardening/host/checks/pam.py 1970-01-01 00:00:00 +0000 |
3130 | @@ -1,134 +0,0 @@ |
3131 | -# Copyright 2016 Canonical Limited. |
3132 | -# |
3133 | -# This file is part of charm-helpers. |
3134 | -# |
3135 | -# charm-helpers is free software: you can redistribute it and/or modify |
3136 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3137 | -# published by the Free Software Foundation. |
3138 | -# |
3139 | -# charm-helpers is distributed in the hope that it will be useful, |
3140 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3141 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3142 | -# GNU Lesser General Public License for more details. |
3143 | -# |
3144 | -# You should have received a copy of the GNU Lesser General Public License |
3145 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3146 | - |
3147 | -from subprocess import ( |
3148 | - check_output, |
3149 | - CalledProcessError, |
3150 | -) |
3151 | - |
3152 | -from charmhelpers.core.hookenv import ( |
3153 | - log, |
3154 | - DEBUG, |
3155 | - ERROR, |
3156 | -) |
3157 | -from charmhelpers.fetch import ( |
3158 | - apt_install, |
3159 | - apt_purge, |
3160 | - apt_update, |
3161 | -) |
3162 | -from charmhelpers.contrib.hardening.audits.file import ( |
3163 | - TemplatedFile, |
3164 | - DeletedFile, |
3165 | -) |
3166 | -from charmhelpers.contrib.hardening import utils |
3167 | -from charmhelpers.contrib.hardening.host import TEMPLATES_DIR |
3168 | - |
3169 | - |
3170 | -def get_audits(): |
3171 | - """Get OS hardening PAM authentication audits. |
3172 | - |
3173 | - :returns: dictionary of audits |
3174 | - """ |
3175 | - audits = [] |
3176 | - |
3177 | - settings = utils.get_settings('os') |
3178 | - |
3179 | - if settings['auth']['pam_passwdqc_enable']: |
3180 | - audits.append(PasswdqcPAM('/etc/passwdqc.conf')) |
3181 | - |
3182 | - if settings['auth']['retries']: |
3183 | - audits.append(Tally2PAM('/usr/share/pam-configs/tally2')) |
3184 | - else: |
3185 | - audits.append(DeletedFile('/usr/share/pam-configs/tally2')) |
3186 | - |
3187 | - return audits |
3188 | - |
3189 | - |
3190 | -class PasswdqcPAMContext(object): |
3191 | - |
3192 | - def __call__(self): |
3193 | - ctxt = {} |
3194 | - settings = utils.get_settings('os') |
3195 | - |
3196 | - ctxt['auth_pam_passwdqc_options'] = \ |
3197 | - settings['auth']['pam_passwdqc_options'] |
3198 | - |
3199 | - return ctxt |
3200 | - |
3201 | - |
3202 | -class PasswdqcPAM(TemplatedFile): |
3203 | - """The PAM Audit verifies the linux PAM settings.""" |
3204 | - def __init__(self, path): |
3205 | - super(PasswdqcPAM, self).__init__(path=path, |
3206 | - template_dir=TEMPLATES_DIR, |
3207 | - context=PasswdqcPAMContext(), |
3208 | - user='root', |
3209 | - group='root', |
3210 | - mode=0o0640) |
3211 | - |
3212 | - def pre_write(self): |
3213 | - # Always remove? |
3214 | - for pkg in ['libpam-ccreds', 'libpam-cracklib']: |
3215 | - log("Purging package '%s'" % pkg, level=DEBUG), |
3216 | - apt_purge(pkg) |
3217 | - |
3218 | - apt_update(fatal=True) |
3219 | - for pkg in ['libpam-passwdqc']: |
3220 | - log("Installing package '%s'" % pkg, level=DEBUG), |
3221 | - apt_install(pkg) |
3222 | - |
3223 | - def post_write(self): |
3224 | - """Updates the PAM configuration after the file has been written""" |
3225 | - try: |
3226 | - check_output(['pam-auth-update', '--package']) |
3227 | - except CalledProcessError as e: |
3228 | - log('Error calling pam-auth-update: %s' % e, level=ERROR) |
3229 | - |
3230 | - |
3231 | -class Tally2PAMContext(object): |
3232 | - |
3233 | - def __call__(self): |
3234 | - ctxt = {} |
3235 | - settings = utils.get_settings('os') |
3236 | - |
3237 | - ctxt['auth_lockout_time'] = settings['auth']['lockout_time'] |
3238 | - ctxt['auth_retries'] = settings['auth']['retries'] |
3239 | - |
3240 | - return ctxt |
3241 | - |
3242 | - |
3243 | -class Tally2PAM(TemplatedFile): |
3244 | - """The PAM Audit verifies the linux PAM settings.""" |
3245 | - def __init__(self, path): |
3246 | - super(Tally2PAM, self).__init__(path=path, |
3247 | - template_dir=TEMPLATES_DIR, |
3248 | - context=Tally2PAMContext(), |
3249 | - user='root', |
3250 | - group='root', |
3251 | - mode=0o0640) |
3252 | - |
3253 | - def pre_write(self): |
3254 | - # Always remove? |
3255 | - apt_purge('libpam-ccreds') |
3256 | - apt_update(fatal=True) |
3257 | - apt_install('libpam-modules') |
3258 | - |
3259 | - def post_write(self): |
3260 | - """Updates the PAM configuration after the file has been written""" |
3261 | - try: |
3262 | - check_output(['pam-auth-update', '--package']) |
3263 | - except CalledProcessError as e: |
3264 | - log('Error calling pam-auth-update: %s' % e, level=ERROR) |
3265 | |
3266 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/profile.py' |
3267 | --- hooks/charmhelpers/contrib/hardening/host/checks/profile.py 2016-04-22 04:35:32 +0000 |
3268 | +++ hooks/charmhelpers/contrib/hardening/host/checks/profile.py 1970-01-01 00:00:00 +0000 |
3269 | @@ -1,45 +0,0 @@ |
3270 | -# Copyright 2016 Canonical Limited. |
3271 | -# |
3272 | -# This file is part of charm-helpers. |
3273 | -# |
3274 | -# charm-helpers is free software: you can redistribute it and/or modify |
3275 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3276 | -# published by the Free Software Foundation. |
3277 | -# |
3278 | -# charm-helpers is distributed in the hope that it will be useful, |
3279 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3280 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3281 | -# GNU Lesser General Public License for more details. |
3282 | -# |
3283 | -# You should have received a copy of the GNU Lesser General Public License |
3284 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3285 | - |
3286 | -from charmhelpers.contrib.hardening.audits.file import TemplatedFile |
3287 | -from charmhelpers.contrib.hardening.host import TEMPLATES_DIR |
3288 | -from charmhelpers.contrib.hardening import utils |
3289 | - |
3290 | - |
3291 | -def get_audits(): |
3292 | - """Get OS hardening profile audits. |
3293 | - |
3294 | - :returns: dictionary of audits |
3295 | - """ |
3296 | - audits = [] |
3297 | - |
3298 | - settings = utils.get_settings('os') |
3299 | - |
3300 | - # If core dumps are not enabled, then don't allow core dumps to be |
3301 | - # created as they may contain sensitive information. |
3302 | - if not settings['security']['kernel_enable_core_dump']: |
3303 | - audits.append(TemplatedFile('/etc/profile.d/pinerolo_profile.sh', |
3304 | - ProfileContext(), |
3305 | - template_dir=TEMPLATES_DIR, |
3306 | - mode=0o0755, user='root', group='root')) |
3307 | - return audits |
3308 | - |
3309 | - |
3310 | -class ProfileContext(object): |
3311 | - |
3312 | - def __call__(self): |
3313 | - ctxt = {} |
3314 | - return ctxt |
3315 | |
3316 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/securetty.py' |
3317 | --- hooks/charmhelpers/contrib/hardening/host/checks/securetty.py 2016-04-22 04:35:32 +0000 |
3318 | +++ hooks/charmhelpers/contrib/hardening/host/checks/securetty.py 1970-01-01 00:00:00 +0000 |
3319 | @@ -1,39 +0,0 @@ |
3320 | -# Copyright 2016 Canonical Limited. |
3321 | -# |
3322 | -# This file is part of charm-helpers. |
3323 | -# |
3324 | -# charm-helpers is free software: you can redistribute it and/or modify |
3325 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3326 | -# published by the Free Software Foundation. |
3327 | -# |
3328 | -# charm-helpers is distributed in the hope that it will be useful, |
3329 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3330 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3331 | -# GNU Lesser General Public License for more details. |
3332 | -# |
3333 | -# You should have received a copy of the GNU Lesser General Public License |
3334 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3335 | - |
3336 | -from charmhelpers.contrib.hardening.audits.file import TemplatedFile |
3337 | -from charmhelpers.contrib.hardening.host import TEMPLATES_DIR |
3338 | -from charmhelpers.contrib.hardening import utils |
3339 | - |
3340 | - |
3341 | -def get_audits(): |
3342 | - """Get OS hardening Secure TTY audits. |
3343 | - |
3344 | - :returns: dictionary of audits |
3345 | - """ |
3346 | - audits = [] |
3347 | - audits.append(TemplatedFile('/etc/securetty', SecureTTYContext(), |
3348 | - template_dir=TEMPLATES_DIR, |
3349 | - mode=0o0400, user='root', group='root')) |
3350 | - return audits |
3351 | - |
3352 | - |
3353 | -class SecureTTYContext(object): |
3354 | - |
3355 | - def __call__(self): |
3356 | - settings = utils.get_settings('os') |
3357 | - ctxt = {'ttys': settings['auth']['root_ttys']} |
3358 | - return ctxt |
3359 | |
3360 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py' |
3361 | --- hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py 2016-04-22 04:35:32 +0000 |
3362 | +++ hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py 1970-01-01 00:00:00 +0000 |
3363 | @@ -1,131 +0,0 @@ |
3364 | -# Copyright 2016 Canonical Limited. |
3365 | -# |
3366 | -# This file is part of charm-helpers. |
3367 | -# |
3368 | -# charm-helpers is free software: you can redistribute it and/or modify |
3369 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3370 | -# published by the Free Software Foundation. |
3371 | -# |
3372 | -# charm-helpers is distributed in the hope that it will be useful, |
3373 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3374 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3375 | -# GNU Lesser General Public License for more details. |
3376 | -# |
3377 | -# You should have received a copy of the GNU Lesser General Public License |
3378 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3379 | - |
3380 | -import subprocess |
3381 | - |
3382 | -from charmhelpers.core.hookenv import ( |
3383 | - log, |
3384 | - INFO, |
3385 | -) |
3386 | -from charmhelpers.contrib.hardening.audits.file import NoSUIDSGIDAudit |
3387 | -from charmhelpers.contrib.hardening import utils |
3388 | - |
3389 | - |
3390 | -BLACKLIST = ['/usr/bin/rcp', '/usr/bin/rlogin', '/usr/bin/rsh', |
3391 | - '/usr/libexec/openssh/ssh-keysign', |
3392 | - '/usr/lib/openssh/ssh-keysign', |
3393 | - '/sbin/netreport', |
3394 | - '/usr/sbin/usernetctl', |
3395 | - '/usr/sbin/userisdnctl', |
3396 | - '/usr/sbin/pppd', |
3397 | - '/usr/bin/lockfile', |
3398 | - '/usr/bin/mail-lock', |
3399 | - '/usr/bin/mail-unlock', |
3400 | - '/usr/bin/mail-touchlock', |
3401 | - '/usr/bin/dotlockfile', |
3402 | - '/usr/bin/arping', |
3403 | - '/usr/sbin/uuidd', |
3404 | - '/usr/bin/mtr', |
3405 | - '/usr/lib/evolution/camel-lock-helper-1.2', |
3406 | - '/usr/lib/pt_chown', |
3407 | - '/usr/lib/eject/dmcrypt-get-device', |
3408 | - '/usr/lib/mc/cons.saver'] |
3409 | - |
3410 | -WHITELIST = ['/bin/mount', '/bin/ping', '/bin/su', '/bin/umount', |
3411 | - '/sbin/pam_timestamp_check', '/sbin/unix_chkpwd', '/usr/bin/at', |
3412 | - '/usr/bin/gpasswd', '/usr/bin/locate', '/usr/bin/newgrp', |
3413 | - '/usr/bin/passwd', '/usr/bin/ssh-agent', |
3414 | - '/usr/libexec/utempter/utempter', '/usr/sbin/lockdev', |
3415 | - '/usr/sbin/sendmail.sendmail', '/usr/bin/expiry', |
3416 | - '/bin/ping6', '/usr/bin/traceroute6.iputils', |
3417 | - '/sbin/mount.nfs', '/sbin/umount.nfs', |
3418 | - '/sbin/mount.nfs4', '/sbin/umount.nfs4', |
3419 | - '/usr/bin/crontab', |
3420 | - '/usr/bin/wall', '/usr/bin/write', |
3421 | - '/usr/bin/screen', |
3422 | - '/usr/bin/mlocate', |
3423 | - '/usr/bin/chage', '/usr/bin/chfn', '/usr/bin/chsh', |
3424 | - '/bin/fusermount', |
3425 | - '/usr/bin/pkexec', |
3426 | - '/usr/bin/sudo', '/usr/bin/sudoedit', |
3427 | - '/usr/sbin/postdrop', '/usr/sbin/postqueue', |
3428 | - '/usr/sbin/suexec', |
3429 | - '/usr/lib/squid/ncsa_auth', '/usr/lib/squid/pam_auth', |
3430 | - '/usr/kerberos/bin/ksu', |
3431 | - '/usr/sbin/ccreds_validate', |
3432 | - '/usr/bin/Xorg', |
3433 | - '/usr/bin/X', |
3434 | - '/usr/lib/dbus-1.0/dbus-daemon-launch-helper', |
3435 | - '/usr/lib/vte/gnome-pty-helper', |
3436 | - '/usr/lib/libvte9/gnome-pty-helper', |
3437 | - '/usr/lib/libvte-2.90-9/gnome-pty-helper'] |
3438 | - |
3439 | - |
3440 | -def get_audits(): |
3441 | - """Get OS hardening suid/sgid audits. |
3442 | - |
3443 | - :returns: dictionary of audits |
3444 | - """ |
3445 | - checks = [] |
3446 | - settings = utils.get_settings('os') |
3447 | - if not settings['security']['suid_sgid_enforce']: |
3448 | - log("Skipping suid/sgid hardening", level=INFO) |
3449 | - return checks |
3450 | - |
3451 | - # Build the blacklist and whitelist of files for suid/sgid checks. |
3452 | - # There are a total of 4 lists: |
3453 | - # 1. the system blacklist |
3454 | - # 2. the system whitelist |
3455 | - # 3. the user blacklist |
3456 | - # 4. the user whitelist |
3457 | - # |
3458 | - # The blacklist is the set of paths which should NOT have the suid/sgid bit |
3459 | - # set and the whitelist is the set of paths which MAY have the suid/sgid |
3460 | - # bit setl. The user whitelist/blacklist effectively override the system |
3461 | - # whitelist/blacklist. |
3462 | - u_b = settings['security']['suid_sgid_blacklist'] |
3463 | - u_w = settings['security']['suid_sgid_whitelist'] |
3464 | - |
3465 | - blacklist = set(BLACKLIST) - set(u_w + u_b) |
3466 | - whitelist = set(WHITELIST) - set(u_b + u_w) |
3467 | - |
3468 | - checks.append(NoSUIDSGIDAudit(blacklist)) |
3469 | - |
3470 | - dry_run = settings['security']['suid_sgid_dry_run_on_unknown'] |
3471 | - |
3472 | - if settings['security']['suid_sgid_remove_from_unknown'] or dry_run: |
3473 | - # If the policy is a dry_run (e.g. complain only) or remove unknown |
3474 | - # suid/sgid bits then find all of the paths which have the suid/sgid |
3475 | - # bit set and then remove the whitelisted paths. |
3476 | - root_path = settings['environment']['root_path'] |
3477 | - unknown_paths = find_paths_with_suid_sgid(root_path) - set(whitelist) |
3478 | - checks.append(NoSUIDSGIDAudit(unknown_paths, unless=dry_run)) |
3479 | - |
3480 | - return checks |
3481 | - |
3482 | - |
3483 | -def find_paths_with_suid_sgid(root_path): |
3484 | - """Finds all paths/files which have an suid/sgid bit enabled. |
3485 | - |
3486 | - Starting with the root_path, this will recursively find all paths which |
3487 | - have an suid or sgid bit set. |
3488 | - """ |
3489 | - cmd = ['find', root_path, '-perm', '-4000', '-o', '-perm', '-2000', |
3490 | - '-type', 'f', '!', '-path', '/proc/*', '-print'] |
3491 | - |
3492 | - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
3493 | - out, _ = p.communicate() |
3494 | - return set(out.split('\n')) |
3495 | |
3496 | === removed file 'hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py' |
3497 | --- hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py 2016-04-22 04:35:32 +0000 |
3498 | +++ hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py 1970-01-01 00:00:00 +0000 |
3499 | @@ -1,211 +0,0 @@ |
3500 | -# Copyright 2016 Canonical Limited. |
3501 | -# |
3502 | -# This file is part of charm-helpers. |
3503 | -# |
3504 | -# charm-helpers is free software: you can redistribute it and/or modify |
3505 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3506 | -# published by the Free Software Foundation. |
3507 | -# |
3508 | -# charm-helpers is distributed in the hope that it will be useful, |
3509 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3510 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3511 | -# GNU Lesser General Public License for more details. |
3512 | -# |
3513 | -# You should have received a copy of the GNU Lesser General Public License |
3514 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3515 | - |
3516 | -import os |
3517 | -import platform |
3518 | -import re |
3519 | -import six |
3520 | -import subprocess |
3521 | - |
3522 | -from charmhelpers.core.hookenv import ( |
3523 | - log, |
3524 | - INFO, |
3525 | - WARNING, |
3526 | -) |
3527 | -from charmhelpers.contrib.hardening import utils |
3528 | -from charmhelpers.contrib.hardening.audits.file import ( |
3529 | - FilePermissionAudit, |
3530 | - TemplatedFile, |
3531 | -) |
3532 | -from charmhelpers.contrib.hardening.host import TEMPLATES_DIR |
3533 | - |
3534 | - |
3535 | -SYSCTL_DEFAULTS = """net.ipv4.ip_forward=%(net_ipv4_ip_forward)s |
3536 | -net.ipv6.conf.all.forwarding=%(net_ipv6_conf_all_forwarding)s |
3537 | -net.ipv4.conf.all.rp_filter=1 |
3538 | -net.ipv4.conf.default.rp_filter=1 |
3539 | -net.ipv4.icmp_echo_ignore_broadcasts=1 |
3540 | -net.ipv4.icmp_ignore_bogus_error_responses=1 |
3541 | -net.ipv4.icmp_ratelimit=100 |
3542 | -net.ipv4.icmp_ratemask=88089 |
3543 | -net.ipv6.conf.all.disable_ipv6=%(net_ipv6_conf_all_disable_ipv6)s |
3544 | -net.ipv4.tcp_timestamps=%(net_ipv4_tcp_timestamps)s |
3545 | -net.ipv4.conf.all.arp_ignore=%(net_ipv4_conf_all_arp_ignore)s |
3546 | -net.ipv4.conf.all.arp_announce=%(net_ipv4_conf_all_arp_announce)s |
3547 | -net.ipv4.tcp_rfc1337=1 |
3548 | -net.ipv4.tcp_syncookies=1 |
3549 | -net.ipv4.conf.all.shared_media=1 |
3550 | -net.ipv4.conf.default.shared_media=1 |
3551 | -net.ipv4.conf.all.accept_source_route=0 |
3552 | -net.ipv4.conf.default.accept_source_route=0 |
3553 | -net.ipv4.conf.all.accept_redirects=0 |
3554 | -net.ipv4.conf.default.accept_redirects=0 |
3555 | -net.ipv6.conf.all.accept_redirects=0 |
3556 | -net.ipv6.conf.default.accept_redirects=0 |
3557 | -net.ipv4.conf.all.secure_redirects=0 |
3558 | -net.ipv4.conf.default.secure_redirects=0 |
3559 | -net.ipv4.conf.all.send_redirects=0 |
3560 | -net.ipv4.conf.default.send_redirects=0 |
3561 | -net.ipv4.conf.all.log_martians=0 |
3562 | -net.ipv6.conf.default.router_solicitations=0 |
3563 | -net.ipv6.conf.default.accept_ra_rtr_pref=0 |
3564 | -net.ipv6.conf.default.accept_ra_pinfo=0 |
3565 | -net.ipv6.conf.default.accept_ra_defrtr=0 |
3566 | -net.ipv6.conf.default.autoconf=0 |
3567 | -net.ipv6.conf.default.dad_transmits=0 |
3568 | -net.ipv6.conf.default.max_addresses=1 |
3569 | -net.ipv6.conf.all.accept_ra=0 |
3570 | -net.ipv6.conf.default.accept_ra=0 |
3571 | -kernel.modules_disabled=%(kernel_modules_disabled)s |
3572 | -kernel.sysrq=%(kernel_sysrq)s |
3573 | -fs.suid_dumpable=%(fs_suid_dumpable)s |
3574 | -kernel.randomize_va_space=2 |
3575 | -""" |
3576 | - |
3577 | - |
3578 | -def get_audits(): |
3579 | - """Get OS hardening sysctl audits. |
3580 | - |
3581 | - :returns: dictionary of audits |
3582 | - """ |
3583 | - audits = [] |
3584 | - settings = utils.get_settings('os') |
3585 | - |
3586 | - # Apply the sysctl settings which are configured to be applied. |
3587 | - audits.append(SysctlConf()) |
3588 | - # Make sure that only root has access to the sysctl.conf file, and |
3589 | - # that it is read-only. |
3590 | - audits.append(FilePermissionAudit('/etc/sysctl.conf', |
3591 | - user='root', |
3592 | - group='root', mode=0o0440)) |
3593 | - # If module loading is not enabled, then ensure that the modules |
3594 | - # file has the appropriate permissions and rebuild the initramfs |
3595 | - if not settings['security']['kernel_enable_module_loading']: |
3596 | - audits.append(ModulesTemplate()) |
3597 | - |
3598 | - return audits |
3599 | - |
3600 | - |
3601 | -class ModulesContext(object): |
3602 | - |
3603 | - def __call__(self): |
3604 | - settings = utils.get_settings('os') |
3605 | - with open('/proc/cpuinfo', 'r') as fd: |
3606 | - cpuinfo = fd.readlines() |
3607 | - |
3608 | - for line in cpuinfo: |
3609 | - match = re.search(r"^vendor_id\s+:\s+(.+)", line) |
3610 | - if match: |
3611 | - vendor = match.group(1) |
3612 | - |
3613 | - if vendor == "GenuineIntel": |
3614 | - vendor = "intel" |
3615 | - elif vendor == "AuthenticAMD": |
3616 | - vendor = "amd" |
3617 | - |
3618 | - ctxt = {'arch': platform.processor(), |
3619 | - 'cpuVendor': vendor, |
3620 | - 'desktop_enable': settings['general']['desktop_enable']} |
3621 | - |
3622 | - return ctxt |
3623 | - |
3624 | - |
3625 | -class ModulesTemplate(object): |
3626 | - |
3627 | - def __init__(self): |
3628 | - super(ModulesTemplate, self).__init__('/etc/initramfs-tools/modules', |
3629 | - ModulesContext(), |
3630 | - templates_dir=TEMPLATES_DIR, |
3631 | - user='root', group='root', |
3632 | - mode=0o0440) |
3633 | - |
3634 | - def post_write(self): |
3635 | - subprocess.check_call(['update-initramfs', '-u']) |
3636 | - |
3637 | - |
3638 | -class SysCtlHardeningContext(object): |
3639 | - def __call__(self): |
3640 | - settings = utils.get_settings('os') |
3641 | - ctxt = {'sysctl': {}} |
3642 | - |
3643 | - log("Applying sysctl settings", level=INFO) |
3644 | - extras = {'net_ipv4_ip_forward': 0, |
3645 | - 'net_ipv6_conf_all_forwarding': 0, |
3646 | - 'net_ipv6_conf_all_disable_ipv6': 1, |
3647 | - 'net_ipv4_tcp_timestamps': 0, |
3648 | - 'net_ipv4_conf_all_arp_ignore': 0, |
3649 | - 'net_ipv4_conf_all_arp_announce': 0, |
3650 | - 'kernel_sysrq': 0, |
3651 | - 'fs_suid_dumpable': 0, |
3652 | - 'kernel_modules_disabled': 1} |
3653 | - |
3654 | - if settings['sysctl']['ipv6_enable']: |
3655 | - extras['net_ipv6_conf_all_disable_ipv6'] = 0 |
3656 | - |
3657 | - if settings['sysctl']['forwarding']: |
3658 | - extras['net_ipv4_ip_forward'] = 1 |
3659 | - extras['net_ipv6_conf_all_forwarding'] = 1 |
3660 | - |
3661 | - if settings['sysctl']['arp_restricted']: |
3662 | - extras['net_ipv4_conf_all_arp_ignore'] = 1 |
3663 | - extras['net_ipv4_conf_all_arp_announce'] = 2 |
3664 | - |
3665 | - if settings['security']['kernel_enable_module_loading']: |
3666 | - extras['kernel_modules_disabled'] = 0 |
3667 | - |
3668 | - if settings['sysctl']['kernel_enable_sysrq']: |
3669 | - sysrq_val = settings['sysctl']['kernel_secure_sysrq'] |
3670 | - extras['kernel_sysrq'] = sysrq_val |
3671 | - |
3672 | - if settings['security']['kernel_enable_core_dump']: |
3673 | - extras['fs_suid_dumpable'] = 1 |
3674 | - |
3675 | - settings.update(extras) |
3676 | - for d in (SYSCTL_DEFAULTS % settings).split(): |
3677 | - d = d.strip().partition('=') |
3678 | - key = d[0].strip() |
3679 | - path = os.path.join('/proc/sys', key.replace('.', '/')) |
3680 | - if not os.path.exists(path): |
3681 | - log("Skipping '%s' since '%s' does not exist" % (key, path), |
3682 | - level=WARNING) |
3683 | - continue |
3684 | - |
3685 | - ctxt['sysctl'][key] = d[2] or None |
3686 | - |
3687 | - # Translate for python3 |
3688 | - return {'sysctl_settings': |
3689 | - [(k, v) for k, v in six.iteritems(ctxt['sysctl'])]} |
3690 | - |
3691 | - |
3692 | -class SysctlConf(TemplatedFile): |
3693 | - """An audit check for sysctl settings.""" |
3694 | - def __init__(self): |
3695 | - self.conffile = '/etc/sysctl.d/99-juju-hardening.conf' |
3696 | - super(SysctlConf, self).__init__(self.conffile, |
3697 | - SysCtlHardeningContext(), |
3698 | - template_dir=TEMPLATES_DIR, |
3699 | - user='root', group='root', |
3700 | - mode=0o0440) |
3701 | - |
3702 | - def post_write(self): |
3703 | - try: |
3704 | - subprocess.check_call(['sysctl', '-p', self.conffile]) |
3705 | - except subprocess.CalledProcessError as e: |
3706 | - # NOTE: on some systems if sysctl cannot apply all settings it |
3707 | - # will return non-zero as well. |
3708 | - log("sysctl command returned an error (maybe some " |
3709 | - "keys could not be set) - %s" % (e), |
3710 | - level=WARNING) |
3711 | |
3712 | === removed directory 'hooks/charmhelpers/contrib/hardening/mysql' |
3713 | === removed file 'hooks/charmhelpers/contrib/hardening/mysql/__init__.py' |
3714 | --- hooks/charmhelpers/contrib/hardening/mysql/__init__.py 2016-04-22 04:35:32 +0000 |
3715 | +++ hooks/charmhelpers/contrib/hardening/mysql/__init__.py 1970-01-01 00:00:00 +0000 |
3716 | @@ -1,19 +0,0 @@ |
3717 | -# Copyright 2016 Canonical Limited. |
3718 | -# |
3719 | -# This file is part of charm-helpers. |
3720 | -# |
3721 | -# charm-helpers is free software: you can redistribute it and/or modify |
3722 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3723 | -# published by the Free Software Foundation. |
3724 | -# |
3725 | -# charm-helpers is distributed in the hope that it will be useful, |
3726 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3727 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3728 | -# GNU Lesser General Public License for more details. |
3729 | -# |
3730 | -# You should have received a copy of the GNU Lesser General Public License |
3731 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3732 | - |
3733 | -from os import path |
3734 | - |
3735 | -TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') |
3736 | |
3737 | === removed directory 'hooks/charmhelpers/contrib/hardening/mysql/checks' |
3738 | === removed file 'hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py' |
3739 | --- hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py 2016-04-22 04:35:32 +0000 |
3740 | +++ hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py 1970-01-01 00:00:00 +0000 |
3741 | @@ -1,31 +0,0 @@ |
3742 | -# Copyright 2016 Canonical Limited. |
3743 | -# |
3744 | -# This file is part of charm-helpers. |
3745 | -# |
3746 | -# charm-helpers is free software: you can redistribute it and/or modify |
3747 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3748 | -# published by the Free Software Foundation. |
3749 | -# |
3750 | -# charm-helpers is distributed in the hope that it will be useful, |
3751 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3752 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3753 | -# GNU Lesser General Public License for more details. |
3754 | -# |
3755 | -# You should have received a copy of the GNU Lesser General Public License |
3756 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3757 | - |
3758 | -from charmhelpers.core.hookenv import ( |
3759 | - log, |
3760 | - DEBUG, |
3761 | -) |
3762 | -from charmhelpers.contrib.hardening.mysql.checks import config |
3763 | - |
3764 | - |
3765 | -def run_mysql_checks(): |
3766 | - log("Starting MySQL hardening checks.", level=DEBUG) |
3767 | - checks = config.get_audits() |
3768 | - for check in checks: |
3769 | - log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) |
3770 | - check.ensure_compliance() |
3771 | - |
3772 | - log("MySQL hardening checks complete.", level=DEBUG) |
3773 | |
3774 | === removed file 'hooks/charmhelpers/contrib/hardening/mysql/checks/config.py' |
3775 | --- hooks/charmhelpers/contrib/hardening/mysql/checks/config.py 2016-04-22 04:35:32 +0000 |
3776 | +++ hooks/charmhelpers/contrib/hardening/mysql/checks/config.py 1970-01-01 00:00:00 +0000 |
3777 | @@ -1,89 +0,0 @@ |
3778 | -# Copyright 2016 Canonical Limited. |
3779 | -# |
3780 | -# This file is part of charm-helpers. |
3781 | -# |
3782 | -# charm-helpers is free software: you can redistribute it and/or modify |
3783 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3784 | -# published by the Free Software Foundation. |
3785 | -# |
3786 | -# charm-helpers is distributed in the hope that it will be useful, |
3787 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3788 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3789 | -# GNU Lesser General Public License for more details. |
3790 | -# |
3791 | -# You should have received a copy of the GNU Lesser General Public License |
3792 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3793 | - |
3794 | -import six |
3795 | -import subprocess |
3796 | - |
3797 | -from charmhelpers.core.hookenv import ( |
3798 | - log, |
3799 | - WARNING, |
3800 | -) |
3801 | -from charmhelpers.contrib.hardening.audits.file import ( |
3802 | - FilePermissionAudit, |
3803 | - DirectoryPermissionAudit, |
3804 | - TemplatedFile, |
3805 | -) |
3806 | -from charmhelpers.contrib.hardening.mysql import TEMPLATES_DIR |
3807 | -from charmhelpers.contrib.hardening import utils |
3808 | - |
3809 | - |
3810 | -def get_audits(): |
3811 | - """Get MySQL hardening config audits. |
3812 | - |
3813 | - :returns: dictionary of audits |
3814 | - """ |
3815 | - if subprocess.call(['which', 'mysql'], stdout=subprocess.PIPE) != 0: |
3816 | - log("MySQL does not appear to be installed on this node - " |
3817 | - "skipping mysql hardening", level=WARNING) |
3818 | - return [] |
3819 | - |
3820 | - settings = utils.get_settings('mysql') |
3821 | - hardening_settings = settings['hardening'] |
3822 | - my_cnf = hardening_settings['mysql-conf'] |
3823 | - |
3824 | - audits = [ |
3825 | - FilePermissionAudit(paths=[my_cnf], user='root', |
3826 | - group='root', mode=0o0600), |
3827 | - |
3828 | - TemplatedFile(hardening_settings['hardening-conf'], |
3829 | - MySQLConfContext(), |
3830 | - TEMPLATES_DIR, |
3831 | - mode=0o0750, |
3832 | - user='mysql', |
3833 | - group='root', |
3834 | - service_actions=[{'service': 'mysql', |
3835 | - 'actions': ['restart']}]), |
3836 | - |
3837 | - # MySQL and Percona charms do not allow configuration of the |
3838 | - # data directory, so use the default. |
3839 | - DirectoryPermissionAudit('/var/lib/mysql', |
3840 | - user='mysql', |
3841 | - group='mysql', |
3842 | - recursive=False, |
3843 | - mode=0o755), |
3844 | - |
3845 | - DirectoryPermissionAudit('/etc/mysql', |
3846 | - user='root', |
3847 | - group='root', |
3848 | - recursive=False, |
3849 | - mode=0o700), |
3850 | - ] |
3851 | - |
3852 | - return audits |
3853 | - |
3854 | - |
3855 | -class MySQLConfContext(object): |
3856 | - """Defines the set of key/value pairs to set in a mysql config file. |
3857 | - |
3858 | - This context, when called, will return a dictionary containing the |
3859 | - key/value pairs of setting to specify in the |
3860 | - /etc/mysql/conf.d/hardening.cnf file. |
3861 | - """ |
3862 | - def __call__(self): |
3863 | - settings = utils.get_settings('mysql') |
3864 | - # Translate for python3 |
3865 | - return {'mysql_settings': |
3866 | - [(k, v) for k, v in six.iteritems(settings['security'])]} |
3867 | |
3868 | === removed directory 'hooks/charmhelpers/contrib/hardening/ssh' |
3869 | === removed file 'hooks/charmhelpers/contrib/hardening/ssh/__init__.py' |
3870 | --- hooks/charmhelpers/contrib/hardening/ssh/__init__.py 2016-04-22 04:35:32 +0000 |
3871 | +++ hooks/charmhelpers/contrib/hardening/ssh/__init__.py 1970-01-01 00:00:00 +0000 |
3872 | @@ -1,19 +0,0 @@ |
3873 | -# Copyright 2016 Canonical Limited. |
3874 | -# |
3875 | -# This file is part of charm-helpers. |
3876 | -# |
3877 | -# charm-helpers is free software: you can redistribute it and/or modify |
3878 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3879 | -# published by the Free Software Foundation. |
3880 | -# |
3881 | -# charm-helpers is distributed in the hope that it will be useful, |
3882 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3883 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3884 | -# GNU Lesser General Public License for more details. |
3885 | -# |
3886 | -# You should have received a copy of the GNU Lesser General Public License |
3887 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3888 | - |
3889 | -from os import path |
3890 | - |
3891 | -TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') |
3892 | |
3893 | === removed directory 'hooks/charmhelpers/contrib/hardening/ssh/checks' |
3894 | === removed file 'hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py' |
3895 | --- hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py 2016-04-22 04:35:32 +0000 |
3896 | +++ hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py 1970-01-01 00:00:00 +0000 |
3897 | @@ -1,31 +0,0 @@ |
3898 | -# Copyright 2016 Canonical Limited. |
3899 | -# |
3900 | -# This file is part of charm-helpers. |
3901 | -# |
3902 | -# charm-helpers is free software: you can redistribute it and/or modify |
3903 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3904 | -# published by the Free Software Foundation. |
3905 | -# |
3906 | -# charm-helpers is distributed in the hope that it will be useful, |
3907 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3908 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3909 | -# GNU Lesser General Public License for more details. |
3910 | -# |
3911 | -# You should have received a copy of the GNU Lesser General Public License |
3912 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3913 | - |
3914 | -from charmhelpers.core.hookenv import ( |
3915 | - log, |
3916 | - DEBUG, |
3917 | -) |
3918 | -from charmhelpers.contrib.hardening.ssh.checks import config |
3919 | - |
3920 | - |
3921 | -def run_ssh_checks(): |
3922 | - log("Starting SSH hardening checks.", level=DEBUG) |
3923 | - checks = config.get_audits() |
3924 | - for check in checks: |
3925 | - log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) |
3926 | - check.ensure_compliance() |
3927 | - |
3928 | - log("SSH hardening checks complete.", level=DEBUG) |
3929 | |
3930 | === removed file 'hooks/charmhelpers/contrib/hardening/ssh/checks/config.py' |
3931 | --- hooks/charmhelpers/contrib/hardening/ssh/checks/config.py 2016-04-22 04:35:32 +0000 |
3932 | +++ hooks/charmhelpers/contrib/hardening/ssh/checks/config.py 1970-01-01 00:00:00 +0000 |
3933 | @@ -1,394 +0,0 @@ |
3934 | -# Copyright 2016 Canonical Limited. |
3935 | -# |
3936 | -# This file is part of charm-helpers. |
3937 | -# |
3938 | -# charm-helpers is free software: you can redistribute it and/or modify |
3939 | -# it under the terms of the GNU Lesser General Public License version 3 as |
3940 | -# published by the Free Software Foundation. |
3941 | -# |
3942 | -# charm-helpers is distributed in the hope that it will be useful, |
3943 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3944 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3945 | -# GNU Lesser General Public License for more details. |
3946 | -# |
3947 | -# You should have received a copy of the GNU Lesser General Public License |
3948 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
3949 | - |
3950 | -import os |
3951 | - |
3952 | -from charmhelpers.core.hookenv import ( |
3953 | - log, |
3954 | - DEBUG, |
3955 | -) |
3956 | -from charmhelpers.fetch import ( |
3957 | - apt_install, |
3958 | - apt_update, |
3959 | -) |
3960 | -from charmhelpers.core.host import lsb_release |
3961 | -from charmhelpers.contrib.hardening.audits.file import ( |
3962 | - TemplatedFile, |
3963 | - FileContentAudit, |
3964 | -) |
3965 | -from charmhelpers.contrib.hardening.ssh import TEMPLATES_DIR |
3966 | -from charmhelpers.contrib.hardening import utils |
3967 | - |
3968 | - |
3969 | -def get_audits(): |
3970 | - """Get SSH hardening config audits. |
3971 | - |
3972 | - :returns: dictionary of audits |
3973 | - """ |
3974 | - audits = [SSHConfig(), SSHDConfig(), SSHConfigFileContentAudit(), |
3975 | - SSHDConfigFileContentAudit()] |
3976 | - return audits |
3977 | - |
3978 | - |
3979 | -class SSHConfigContext(object): |
3980 | - |
3981 | - type = 'client' |
3982 | - |
3983 | - def get_macs(self, allow_weak_mac): |
3984 | - if allow_weak_mac: |
3985 | - weak_macs = 'weak' |
3986 | - else: |
3987 | - weak_macs = 'default' |
3988 | - |
3989 | - default = 'hmac-sha2-512,hmac-sha2-256,hmac-ripemd160' |
3990 | - macs = {'default': default, |
3991 | - 'weak': default + ',hmac-sha1'} |
3992 | - |
3993 | - default = ('hmac-sha2-512-etm@openssh.com,' |
3994 | - 'hmac-sha2-256-etm@openssh.com,' |
3995 | - 'hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,' |
3996 | - 'hmac-sha2-512,hmac-sha2-256,hmac-ripemd160') |
3997 | - macs_66 = {'default': default, |
3998 | - 'weak': default + ',hmac-sha1'} |
3999 | - |
4000 | - # Use newer ciphers on Ubuntu Trusty and above |
4001 | - if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': |
4002 | - log("Detected Ubuntu 14.04 or newer, using new macs", level=DEBUG) |
4003 | - macs = macs_66 |
4004 | - |
4005 | - return macs[weak_macs] |
4006 | - |
4007 | - def get_kexs(self, allow_weak_kex): |
4008 | - if allow_weak_kex: |
4009 | - weak_kex = 'weak' |
4010 | - else: |
4011 | - weak_kex = 'default' |
4012 | - |
4013 | - default = 'diffie-hellman-group-exchange-sha256' |
4014 | - weak = (default + ',diffie-hellman-group14-sha1,' |
4015 | - 'diffie-hellman-group-exchange-sha1,' |
4016 | - 'diffie-hellman-group1-sha1') |
4017 | - kex = {'default': default, |
4018 | - 'weak': weak} |
4019 | - |
4020 | - default = ('curve25519-sha256@libssh.org,' |
4021 | - 'diffie-hellman-group-exchange-sha256') |
4022 | - weak = (default + ',diffie-hellman-group14-sha1,' |
4023 | - 'diffie-hellman-group-exchange-sha1,' |
4024 | - 'diffie-hellman-group1-sha1') |
4025 | - kex_66 = {'default': default, |
4026 | - 'weak': weak} |
4027 | - |
4028 | - # Use newer kex on Ubuntu Trusty and above |
4029 | - if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': |
4030 | - log('Detected Ubuntu 14.04 or newer, using new key exchange ' |
4031 | - 'algorithms', level=DEBUG) |
4032 | - kex = kex_66 |
4033 | - |
4034 | - return kex[weak_kex] |
4035 | - |
4036 | - def get_ciphers(self, cbc_required): |
4037 | - if cbc_required: |
4038 | - weak_ciphers = 'weak' |
4039 | - else: |
4040 | - weak_ciphers = 'default' |
4041 | - |
4042 | - default = 'aes256-ctr,aes192-ctr,aes128-ctr' |
4043 | - cipher = {'default': default, |
4044 | - 'weak': default + 'aes256-cbc,aes192-cbc,aes128-cbc'} |
4045 | - |
4046 | - default = ('chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,' |
4047 | - 'aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr') |
4048 | - ciphers_66 = {'default': default, |
4049 | - 'weak': default + ',aes256-cbc,aes192-cbc,aes128-cbc'} |
4050 | - |
4051 | - # Use newer ciphers on ubuntu Trusty and above |
4052 | - if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': |
4053 | - log('Detected Ubuntu 14.04 or newer, using new ciphers', |
4054 | - level=DEBUG) |
4055 | - cipher = ciphers_66 |
4056 | - |
4057 | - return cipher[weak_ciphers] |
4058 | - |
4059 | - def __call__(self): |
4060 | - settings = utils.get_settings('ssh') |
4061 | - if settings['common']['network_ipv6_enable']: |
4062 | - addr_family = 'any' |
4063 | - else: |
4064 | - addr_family = 'inet' |
4065 | - |
4066 | - ctxt = { |
4067 | - 'addr_family': addr_family, |
4068 | - 'remote_hosts': settings['common']['remote_hosts'], |
4069 | - 'password_auth_allowed': |
4070 | - settings['client']['password_authentication'], |
4071 | - 'ports': settings['common']['ports'], |
4072 | - 'ciphers': self.get_ciphers(settings['client']['cbc_required']), |
4073 | - 'macs': self.get_macs(settings['client']['weak_hmac']), |
4074 | - 'kexs': self.get_kexs(settings['client']['weak_kex']), |
4075 | - 'roaming': settings['client']['roaming'], |
4076 | - } |
4077 | - return ctxt |
4078 | - |
4079 | - |
4080 | -class SSHConfig(TemplatedFile): |
4081 | - def __init__(self): |
4082 | - path = '/etc/ssh/ssh_config' |
4083 | - super(SSHConfig, self).__init__(path=path, |
4084 | - template_dir=TEMPLATES_DIR, |
4085 | - context=SSHConfigContext(), |
4086 | - user='root', |
4087 | - group='root', |
4088 | - mode=0o0644) |
4089 | - |
4090 | - def pre_write(self): |
4091 | - settings = utils.get_settings('ssh') |
4092 | - apt_update(fatal=True) |
4093 | - apt_install(settings['client']['package']) |
4094 | - if not os.path.exists('/etc/ssh'): |
4095 | - os.makedir('/etc/ssh') |
4096 | - # NOTE: don't recurse |
4097 | - utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755, |
4098 | - maxdepth=0) |
4099 | - |
4100 | - def post_write(self): |
4101 | - # NOTE: don't recurse |
4102 | - utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755, |
4103 | - maxdepth=0) |
4104 | - |
4105 | - |
4106 | -class SSHDConfigContext(SSHConfigContext): |
4107 | - |
4108 | - type = 'server' |
4109 | - |
4110 | - def __call__(self): |
4111 | - settings = utils.get_settings('ssh') |
4112 | - if settings['common']['network_ipv6_enable']: |
4113 | - addr_family = 'any' |
4114 | - else: |
4115 | - addr_family = 'inet' |
4116 | - |
4117 | - ctxt = { |
4118 | - 'ssh_ip': settings['server']['listen_to'], |
4119 | - 'password_auth_allowed': |
4120 | - settings['server']['password_authentication'], |
4121 | - 'ports': settings['common']['ports'], |
4122 | - 'addr_family': addr_family, |
4123 | - 'ciphers': self.get_ciphers(settings['server']['cbc_required']), |
4124 | - 'macs': self.get_macs(settings['server']['weak_hmac']), |
4125 | - 'kexs': self.get_kexs(settings['server']['weak_kex']), |
4126 | - 'host_key_files': settings['server']['host_key_files'], |
4127 | - 'allow_root_with_key': settings['server']['allow_root_with_key'], |
4128 | - 'password_authentication': |
4129 | - settings['server']['password_authentication'], |
4130 | - 'use_priv_sep': settings['server']['use_privilege_separation'], |
4131 | - 'use_pam': settings['server']['use_pam'], |
4132 | - 'allow_x11_forwarding': settings['server']['allow_x11_forwarding'], |
4133 | - 'print_motd': settings['server']['print_motd'], |
4134 | - 'print_last_log': settings['server']['print_last_log'], |
4135 | - 'client_alive_interval': |
4136 | - settings['server']['alive_interval'], |
4137 | - 'client_alive_count': settings['server']['alive_count'], |
4138 | - 'allow_tcp_forwarding': settings['server']['allow_tcp_forwarding'], |
4139 | - 'allow_agent_forwarding': |
4140 | - settings['server']['allow_agent_forwarding'], |
4141 | - 'deny_users': settings['server']['deny_users'], |
4142 | - 'allow_users': settings['server']['allow_users'], |
4143 | - 'deny_groups': settings['server']['deny_groups'], |
4144 | - 'allow_groups': settings['server']['allow_groups'], |
4145 | - 'use_dns': settings['server']['use_dns'], |
4146 | - 'sftp_enable': settings['server']['sftp_enable'], |
4147 | - 'sftp_group': settings['server']['sftp_group'], |
4148 | - 'sftp_chroot': settings['server']['sftp_chroot'], |
4149 | - 'max_auth_tries': settings['server']['max_auth_tries'], |
4150 | - 'max_sessions': settings['server']['max_sessions'], |
4151 | - } |
4152 | - return ctxt |
4153 | - |
4154 | - |
4155 | -class SSHDConfig(TemplatedFile): |
4156 | - def __init__(self): |
4157 | - path = '/etc/ssh/sshd_config' |
4158 | - super(SSHDConfig, self).__init__(path=path, |
4159 | - template_dir=TEMPLATES_DIR, |
4160 | - context=SSHDConfigContext(), |
4161 | - user='root', |
4162 | - group='root', |
4163 | - mode=0o0600, |
4164 | - service_actions=[{'service': 'ssh', |
4165 | - 'actions': |
4166 | - ['restart']}]) |
4167 | - |
4168 | - def pre_write(self): |
4169 | - settings = utils.get_settings('ssh') |
4170 | - apt_update(fatal=True) |
4171 | - apt_install(settings['server']['package']) |
4172 | - if not os.path.exists('/etc/ssh'): |
4173 | - os.makedir('/etc/ssh') |
4174 | - # NOTE: don't recurse |
4175 | - utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755, |
4176 | - maxdepth=0) |
4177 | - |
4178 | - def post_write(self): |
4179 | - # NOTE: don't recurse |
4180 | - utils.ensure_permissions('/etc/ssh', 'root', 'root', 0o0755, |
4181 | - maxdepth=0) |
4182 | - |
4183 | - |
4184 | -class SSHConfigFileContentAudit(FileContentAudit): |
4185 | - def __init__(self): |
4186 | - self.path = '/etc/ssh/ssh_config' |
4187 | - super(SSHConfigFileContentAudit, self).__init__(self.path, {}) |
4188 | - |
4189 | - def is_compliant(self, *args, **kwargs): |
4190 | - self.pass_cases = [] |
4191 | - self.fail_cases = [] |
4192 | - settings = utils.get_settings('ssh') |
4193 | - |
4194 | - if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': |
4195 | - if not settings['server']['weak_hmac']: |
4196 | - self.pass_cases.append(r'^MACs.+,hmac-ripemd160$') |
4197 | - else: |
4198 | - self.pass_cases.append(r'^MACs.+,hmac-sha1$') |
4199 | - |
4200 | - if settings['server']['weak_kex']: |
4201 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa |
4202 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa |
4203 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa |
4204 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa |
4205 | - else: |
4206 | - self.pass_cases.append(r'^KexAlgorithms.+,diffie-hellman-group-exchange-sha256$') # noqa |
4207 | - self.fail_cases.append(r'^KexAlgorithms.*diffie-hellman-group14-sha1[,\s]?') # noqa |
4208 | - |
4209 | - if settings['server']['cbc_required']: |
4210 | - self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4211 | - self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?') |
4212 | - self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4213 | - self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4214 | - else: |
4215 | - self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4216 | - self.pass_cases.append(r'^Ciphers\schacha20-poly1305@openssh.com,.+') # noqa |
4217 | - self.pass_cases.append(r'^Ciphers\s.*aes128-ctr$') |
4218 | - self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4219 | - self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4220 | - else: |
4221 | - if not settings['client']['weak_hmac']: |
4222 | - self.fail_cases.append(r'^MACs.+,hmac-sha1$') |
4223 | - else: |
4224 | - self.pass_cases.append(r'^MACs.+,hmac-sha1$') |
4225 | - |
4226 | - if settings['client']['weak_kex']: |
4227 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa |
4228 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa |
4229 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa |
4230 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa |
4231 | - else: |
4232 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256$') # noqa |
4233 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa |
4234 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa |
4235 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa |
4236 | - |
4237 | - if settings['client']['cbc_required']: |
4238 | - self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4239 | - self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?') |
4240 | - self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4241 | - self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4242 | - else: |
4243 | - self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4244 | - self.pass_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?') |
4245 | - self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4246 | - self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4247 | - |
4248 | - if settings['client']['roaming']: |
4249 | - self.pass_cases.append(r'^UseRoaming yes$') |
4250 | - else: |
4251 | - self.fail_cases.append(r'^UseRoaming yes$') |
4252 | - |
4253 | - return super(SSHConfigFileContentAudit, self).is_compliant(*args, |
4254 | - **kwargs) |
4255 | - |
4256 | - |
4257 | -class SSHDConfigFileContentAudit(FileContentAudit): |
4258 | - def __init__(self): |
4259 | - self.path = '/etc/ssh/sshd_config' |
4260 | - super(SSHDConfigFileContentAudit, self).__init__(self.path, {}) |
4261 | - |
4262 | - def is_compliant(self, *args, **kwargs): |
4263 | - self.pass_cases = [] |
4264 | - self.fail_cases = [] |
4265 | - settings = utils.get_settings('ssh') |
4266 | - |
4267 | - if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': |
4268 | - if not settings['server']['weak_hmac']: |
4269 | - self.pass_cases.append(r'^MACs.+,hmac-ripemd160$') |
4270 | - else: |
4271 | - self.pass_cases.append(r'^MACs.+,hmac-sha1$') |
4272 | - |
4273 | - if settings['server']['weak_kex']: |
4274 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa |
4275 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa |
4276 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa |
4277 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa |
4278 | - else: |
4279 | - self.pass_cases.append(r'^KexAlgorithms.+,diffie-hellman-group-exchange-sha256$') # noqa |
4280 | - self.fail_cases.append(r'^KexAlgorithms.*diffie-hellman-group14-sha1[,\s]?') # noqa |
4281 | - |
4282 | - if settings['server']['cbc_required']: |
4283 | - self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4284 | - self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?') |
4285 | - self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4286 | - self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4287 | - else: |
4288 | - self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4289 | - self.pass_cases.append(r'^Ciphers\schacha20-poly1305@openssh.com,.+') # noqa |
4290 | - self.pass_cases.append(r'^Ciphers\s.*aes128-ctr$') |
4291 | - self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4292 | - self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4293 | - else: |
4294 | - if not settings['server']['weak_hmac']: |
4295 | - self.pass_cases.append(r'^MACs.+,hmac-ripemd160$') |
4296 | - else: |
4297 | - self.pass_cases.append(r'^MACs.+,hmac-sha1$') |
4298 | - |
4299 | - if settings['server']['weak_kex']: |
4300 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256[,\s]?') # noqa |
4301 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa |
4302 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa |
4303 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa |
4304 | - else: |
4305 | - self.pass_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha256$') # noqa |
4306 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group14-sha1[,\s]?') # noqa |
4307 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group-exchange-sha1[,\s]?') # noqa |
4308 | - self.fail_cases.append(r'^KexAlgorithms\sdiffie-hellman-group1-sha1[,\s]?') # noqa |
4309 | - |
4310 | - if settings['server']['cbc_required']: |
4311 | - self.pass_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4312 | - self.fail_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?') |
4313 | - self.fail_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4314 | - self.fail_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4315 | - else: |
4316 | - self.fail_cases.append(r'^Ciphers\s.*-cbc[,\s]?') |
4317 | - self.pass_cases.append(r'^Ciphers\s.*aes128-ctr[,\s]?') |
4318 | - self.pass_cases.append(r'^Ciphers\s.*aes192-ctr[,\s]?') |
4319 | - self.pass_cases.append(r'^Ciphers\s.*aes256-ctr[,\s]?') |
4320 | - |
4321 | - if settings['server']['sftp_enable']: |
4322 | - self.pass_cases.append(r'^Subsystem\ssftp') |
4323 | - else: |
4324 | - self.fail_cases.append(r'^Subsystem\ssftp') |
4325 | - |
4326 | - return super(SSHDConfigFileContentAudit, self).is_compliant(*args, |
4327 | - **kwargs) |
4328 | |
4329 | === removed file 'hooks/charmhelpers/contrib/hardening/templating.py' |
4330 | --- hooks/charmhelpers/contrib/hardening/templating.py 2016-04-22 04:35:32 +0000 |
4331 | +++ hooks/charmhelpers/contrib/hardening/templating.py 1970-01-01 00:00:00 +0000 |
4332 | @@ -1,71 +0,0 @@ |
4333 | -# Copyright 2016 Canonical Limited. |
4334 | -# |
4335 | -# This file is part of charm-helpers. |
4336 | -# |
4337 | -# charm-helpers is free software: you can redistribute it and/or modify |
4338 | -# it under the terms of the GNU Lesser General Public License version 3 as |
4339 | -# published by the Free Software Foundation. |
4340 | -# |
4341 | -# charm-helpers is distributed in the hope that it will be useful, |
4342 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4343 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4344 | -# GNU Lesser General Public License for more details. |
4345 | -# |
4346 | -# You should have received a copy of the GNU Lesser General Public License |
4347 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
4348 | - |
4349 | -import os |
4350 | - |
4351 | -from charmhelpers.core.hookenv import ( |
4352 | - log, |
4353 | - DEBUG, |
4354 | - WARNING, |
4355 | -) |
4356 | - |
4357 | -try: |
4358 | - from jinja2 import FileSystemLoader, Environment |
4359 | -except ImportError: |
4360 | - from charmhelpers.fetch import apt_install |
4361 | - from charmhelpers.fetch import apt_update |
4362 | - apt_update(fatal=True) |
4363 | - apt_install('python-jinja2', fatal=True) |
4364 | - from jinja2 import FileSystemLoader, Environment |
4365 | - |
4366 | - |
4367 | -# NOTE: function separated from main rendering code to facilitate easier |
4368 | -# mocking in unit tests. |
4369 | -def write(path, data): |
4370 | - with open(path, 'wb') as out: |
4371 | - out.write(data) |
4372 | - |
4373 | - |
4374 | -def get_template_path(template_dir, path): |
4375 | - """Returns the template file which would be used to render the path. |
4376 | - |
4377 | - The path to the template file is returned. |
4378 | - :param template_dir: the directory the templates are located in |
4379 | - :param path: the file path to be written to. |
4380 | - :returns: path to the template file |
4381 | - """ |
4382 | - return os.path.join(template_dir, os.path.basename(path)) |
4383 | - |
4384 | - |
4385 | -def render_and_write(template_dir, path, context): |
4386 | - """Renders the specified template into the file. |
4387 | - |
4388 | - :param template_dir: the directory to load the template from |
4389 | - :param path: the path to write the templated contents to |
4390 | - :param context: the parameters to pass to the rendering engine |
4391 | - """ |
4392 | - env = Environment(loader=FileSystemLoader(template_dir)) |
4393 | - template_file = os.path.basename(path) |
4394 | - template = env.get_template(template_file) |
4395 | - log('Rendering from template: %s' % template.name, level=DEBUG) |
4396 | - rendered_content = template.render(context) |
4397 | - if not rendered_content: |
4398 | - log("Render returned None - skipping '%s'" % path, |
4399 | - level=WARNING) |
4400 | - return |
4401 | - |
4402 | - write(path, rendered_content.encode('utf-8').strip()) |
4403 | - log('Wrote template %s' % path, level=DEBUG) |
4404 | |
4405 | === removed file 'hooks/charmhelpers/contrib/hardening/utils.py' |
4406 | --- hooks/charmhelpers/contrib/hardening/utils.py 2016-04-22 04:35:32 +0000 |
4407 | +++ hooks/charmhelpers/contrib/hardening/utils.py 1970-01-01 00:00:00 +0000 |
4408 | @@ -1,157 +0,0 @@ |
4409 | -# Copyright 2016 Canonical Limited. |
4410 | -# |
4411 | -# This file is part of charm-helpers. |
4412 | -# |
4413 | -# charm-helpers is free software: you can redistribute it and/or modify |
4414 | -# it under the terms of the GNU Lesser General Public License version 3 as |
4415 | -# published by the Free Software Foundation. |
4416 | -# |
4417 | -# charm-helpers is distributed in the hope that it will be useful, |
4418 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4419 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4420 | -# GNU Lesser General Public License for more details. |
4421 | -# |
4422 | -# You should have received a copy of the GNU Lesser General Public License |
4423 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
4424 | - |
4425 | -import glob |
4426 | -import grp |
4427 | -import os |
4428 | -import pwd |
4429 | -import six |
4430 | -import yaml |
4431 | - |
4432 | -from charmhelpers.core.hookenv import ( |
4433 | - log, |
4434 | - DEBUG, |
4435 | - INFO, |
4436 | - WARNING, |
4437 | - ERROR, |
4438 | -) |
4439 | - |
4440 | - |
4441 | -# Global settings cache. Since each hook fire entails a fresh module import it |
4442 | -# is safe to hold this in memory and not risk missing config changes (since |
4443 | -# they will result in a new hook fire and thus re-import). |
4444 | -__SETTINGS__ = {} |
4445 | - |
4446 | - |
4447 | -def _get_defaults(modules): |
4448 | - """Load the default config for the provided modules. |
4449 | - |
4450 | - :param modules: stack modules config defaults to lookup. |
4451 | - :returns: modules default config dictionary. |
4452 | - """ |
4453 | - default = os.path.join(os.path.dirname(__file__), |
4454 | - 'defaults/%s.yaml' % (modules)) |
4455 | - return yaml.safe_load(open(default)) |
4456 | - |
4457 | - |
4458 | -def _get_schema(modules): |
4459 | - """Load the config schema for the provided modules. |
4460 | - |
4461 | - NOTE: this schema is intended to have 1-1 relationship with they keys in |
4462 | - the default config and is used a means to verify valid overrides provided |
4463 | - by the user. |
4464 | - |
4465 | - :param modules: stack modules config schema to lookup. |
4466 | - :returns: modules default schema dictionary. |
4467 | - """ |
4468 | - schema = os.path.join(os.path.dirname(__file__), |
4469 | - 'defaults/%s.yaml.schema' % (modules)) |
4470 | - return yaml.safe_load(open(schema)) |
4471 | - |
4472 | - |
4473 | -def _get_user_provided_overrides(modules): |
4474 | - """Load user-provided config overrides. |
4475 | - |
4476 | - :param modules: stack modules to lookup in user overrides yaml file. |
4477 | - :returns: overrides dictionary. |
4478 | - """ |
4479 | - overrides = os.path.join(os.environ['JUJU_CHARM_DIR'], |
4480 | - 'hardening.yaml') |
4481 | - if os.path.exists(overrides): |
4482 | - log("Found user-provided config overrides file '%s'" % |
4483 | - (overrides), level=DEBUG) |
4484 | - settings = yaml.safe_load(open(overrides)) |
4485 | - if settings and settings.get(modules): |
4486 | - log("Applying '%s' overrides" % (modules), level=DEBUG) |
4487 | - return settings.get(modules) |
4488 | - |
4489 | - log("No overrides found for '%s'" % (modules), level=DEBUG) |
4490 | - else: |
4491 | - log("No hardening config overrides file '%s' found in charm " |
4492 | - "root dir" % (overrides), level=DEBUG) |
4493 | - |
4494 | - return {} |
4495 | - |
4496 | - |
4497 | -def _apply_overrides(settings, overrides, schema): |
4498 | - """Get overrides config overlayed onto modules defaults. |
4499 | - |
4500 | - :param modules: require stack modules config. |
4501 | - :returns: dictionary of modules config with user overrides applied. |
4502 | - """ |
4503 | - if overrides: |
4504 | - for k, v in six.iteritems(overrides): |
4505 | - if k in schema: |
4506 | - if schema[k] is None: |
4507 | - settings[k] = v |
4508 | - elif type(schema[k]) is dict: |
4509 | - settings[k] = _apply_overrides(settings[k], overrides[k], |
4510 | - schema[k]) |
4511 | - else: |
4512 | - raise Exception("Unexpected type found in schema '%s'" % |
4513 | - type(schema[k]), level=ERROR) |
4514 | - else: |
4515 | - log("Unknown override key '%s' - ignoring" % (k), level=INFO) |
4516 | - |
4517 | - return settings |
4518 | - |
4519 | - |
4520 | -def get_settings(modules): |
4521 | - global __SETTINGS__ |
4522 | - if modules in __SETTINGS__: |
4523 | - return __SETTINGS__[modules] |
4524 | - |
4525 | - schema = _get_schema(modules) |
4526 | - settings = _get_defaults(modules) |
4527 | - overrides = _get_user_provided_overrides(modules) |
4528 | - __SETTINGS__[modules] = _apply_overrides(settings, overrides, schema) |
4529 | - return __SETTINGS__[modules] |
4530 | - |
4531 | - |
4532 | -def ensure_permissions(path, user, group, permissions, maxdepth=-1): |
4533 | - """Ensure permissions for path. |
4534 | - |
4535 | - If path is a file, apply to file and return. If path is a directory, |
4536 | - apply recursively (if required) to directory contents and return. |
4537 | - |
4538 | - :param user: user name |
4539 | - :param group: group name |
4540 | - :param permissions: octal permissions |
4541 | - :param maxdepth: maximum recursion depth. A negative maxdepth allows |
4542 | - infinite recursion and maxdepth=0 means no recursion. |
4543 | - :returns: None |
4544 | - """ |
4545 | - if not os.path.exists(path): |
4546 | - log("File '%s' does not exist - cannot set permissions" % (path), |
4547 | - level=WARNING) |
4548 | - return |
4549 | - |
4550 | - _user = pwd.getpwnam(user) |
4551 | - os.chown(path, _user.pw_uid, grp.getgrnam(group).gr_gid) |
4552 | - os.chmod(path, permissions) |
4553 | - |
4554 | - if maxdepth == 0: |
4555 | - log("Max recursion depth reached - skipping further recursion", |
4556 | - level=DEBUG) |
4557 | - return |
4558 | - elif maxdepth > 0: |
4559 | - maxdepth -= 1 |
4560 | - |
4561 | - if os.path.isdir(path): |
4562 | - contents = glob.glob("%s/*" % (path)) |
4563 | - for c in contents: |
4564 | - ensure_permissions(c, user=user, group=group, |
4565 | - permissions=permissions, maxdepth=maxdepth) |
4566 | |
4567 | === removed directory 'hooks/charmhelpers/contrib/mellanox' |
4568 | === removed file 'hooks/charmhelpers/contrib/mellanox/__init__.py' |
4569 | === removed file 'hooks/charmhelpers/contrib/mellanox/infiniband.py' |
4570 | --- hooks/charmhelpers/contrib/mellanox/infiniband.py 2016-03-07 20:31:20 +0000 |
4571 | +++ hooks/charmhelpers/contrib/mellanox/infiniband.py 1970-01-01 00:00:00 +0000 |
4572 | @@ -1,151 +0,0 @@ |
4573 | -#!/usr/bin/env python |
4574 | -# -*- coding: utf-8 -*- |
4575 | - |
4576 | -# Copyright 2014-2015 Canonical Limited. |
4577 | -# |
4578 | -# This file is part of charm-helpers. |
4579 | -# |
4580 | -# charm-helpers is free software: you can redistribute it and/or modify |
4581 | -# it under the terms of the GNU Lesser General Public License version 3 as |
4582 | -# published by the Free Software Foundation. |
4583 | -# |
4584 | -# charm-helpers is distributed in the hope that it will be useful, |
4585 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4586 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4587 | -# GNU Lesser General Public License for more details. |
4588 | -# |
4589 | -# You should have received a copy of the GNU Lesser General Public License |
4590 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
4591 | - |
4592 | - |
4593 | -__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" |
4594 | - |
4595 | -from charmhelpers.fetch import ( |
4596 | - apt_install, |
4597 | - apt_update, |
4598 | -) |
4599 | - |
4600 | -from charmhelpers.core.hookenv import ( |
4601 | - log, |
4602 | - INFO, |
4603 | -) |
4604 | - |
4605 | -try: |
4606 | - from netifaces import interfaces as network_interfaces |
4607 | -except ImportError: |
4608 | - apt_install('python-netifaces') |
4609 | - from netifaces import interfaces as network_interfaces |
4610 | - |
4611 | -import os |
4612 | -import re |
4613 | -import subprocess |
4614 | - |
4615 | -from charmhelpers.core.kernel import modprobe |
4616 | - |
4617 | -REQUIRED_MODULES = ( |
4618 | - "mlx4_ib", |
4619 | - "mlx4_en", |
4620 | - "mlx4_core", |
4621 | - "ib_ipath", |
4622 | - "ib_mthca", |
4623 | - "ib_srpt", |
4624 | - "ib_srp", |
4625 | - "ib_ucm", |
4626 | - "ib_isert", |
4627 | - "ib_iser", |
4628 | - "ib_ipoib", |
4629 | - "ib_cm", |
4630 | - "ib_uverbs" |
4631 | - "ib_umad", |
4632 | - "ib_sa", |
4633 | - "ib_mad", |
4634 | - "ib_core", |
4635 | - "ib_addr", |
4636 | - "rdma_ucm", |
4637 | -) |
4638 | - |
4639 | -REQUIRED_PACKAGES = ( |
4640 | - "ibutils", |
4641 | - "infiniband-diags", |
4642 | - "ibverbs-utils", |
4643 | -) |
4644 | - |
4645 | -IPOIB_DRIVERS = ( |
4646 | - "ib_ipoib", |
4647 | -) |
4648 | - |
4649 | -ABI_VERSION_FILE = "/sys/class/infiniband_mad/abi_version" |
4650 | - |
4651 | - |
4652 | -class DeviceInfo(object): |
4653 | - pass |
4654 | - |
4655 | - |
4656 | -def install_packages(): |
4657 | - apt_update() |
4658 | - apt_install(REQUIRED_PACKAGES, fatal=True) |
4659 | - |
4660 | - |
4661 | -def load_modules(): |
4662 | - for module in REQUIRED_MODULES: |
4663 | - modprobe(module, persist=True) |
4664 | - |
4665 | - |
4666 | -def is_enabled(): |
4667 | - """Check if infiniband is loaded on the system""" |
4668 | - return os.path.exists(ABI_VERSION_FILE) |
4669 | - |
4670 | - |
4671 | -def stat(): |
4672 | - """Return full output of ibstat""" |
4673 | - return subprocess.check_output(["ibstat"]) |
4674 | - |
4675 | - |
4676 | -def devices(): |
4677 | - """Returns a list of IB enabled devices""" |
4678 | - return subprocess.check_output(['ibstat', '-l']).splitlines() |
4679 | - |
4680 | - |
4681 | -def device_info(device): |
4682 | - """Returns a DeviceInfo object with the current device settings""" |
4683 | - |
4684 | - status = subprocess.check_output([ |
4685 | - 'ibstat', device, '-s']).splitlines() |
4686 | - |
4687 | - regexes = { |
4688 | - "CA type: (.*)": "device_type", |
4689 | - "Number of ports: (.*)": "num_ports", |
4690 | - "Firmware version: (.*)": "fw_ver", |
4691 | - "Hardware version: (.*)": "hw_ver", |
4692 | - "Node GUID: (.*)": "node_guid", |
4693 | - "System image GUID: (.*)": "sys_guid", |
4694 | - } |
4695 | - |
4696 | - device = DeviceInfo() |
4697 | - |
4698 | - for line in status: |
4699 | - for expression, key in regexes.items(): |
4700 | - matches = re.search(expression, line) |
4701 | - if matches: |
4702 | - setattr(device, key, matches.group(1)) |
4703 | - |
4704 | - return device |
4705 | - |
4706 | - |
4707 | -def ipoib_interfaces(): |
4708 | - """Return a list of IPOIB capable ethernet interfaces""" |
4709 | - interfaces = [] |
4710 | - |
4711 | - for interface in network_interfaces(): |
4712 | - try: |
4713 | - driver = re.search('^driver: (.+)$', subprocess.check_output([ |
4714 | - 'ethtool', '-i', |
4715 | - interface]), re.M).group(1) |
4716 | - |
4717 | - if driver in IPOIB_DRIVERS: |
4718 | - interfaces.append(interface) |
4719 | - except: |
4720 | - log("Skipping interface %s" % interface, level=INFO) |
4721 | - continue |
4722 | - |
4723 | - return interfaces |
4724 | |
4725 | === removed directory 'hooks/charmhelpers/contrib/peerstorage' |
4726 | === removed file 'hooks/charmhelpers/contrib/peerstorage/__init__.py' |
4727 | --- hooks/charmhelpers/contrib/peerstorage/__init__.py 2016-02-27 19:51:32 +0000 |
4728 | +++ hooks/charmhelpers/contrib/peerstorage/__init__.py 1970-01-01 00:00:00 +0000 |
4729 | @@ -1,269 +0,0 @@ |
4730 | -# Copyright 2014-2015 Canonical Limited. |
4731 | -# |
4732 | -# This file is part of charm-helpers. |
4733 | -# |
4734 | -# charm-helpers is free software: you can redistribute it and/or modify |
4735 | -# it under the terms of the GNU Lesser General Public License version 3 as |
4736 | -# published by the Free Software Foundation. |
4737 | -# |
4738 | -# charm-helpers is distributed in the hope that it will be useful, |
4739 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4740 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4741 | -# GNU Lesser General Public License for more details. |
4742 | -# |
4743 | -# You should have received a copy of the GNU Lesser General Public License |
4744 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
4745 | - |
4746 | -import json |
4747 | -import six |
4748 | - |
4749 | -from charmhelpers.core.hookenv import relation_id as current_relation_id |
4750 | -from charmhelpers.core.hookenv import ( |
4751 | - is_relation_made, |
4752 | - relation_ids, |
4753 | - relation_get as _relation_get, |
4754 | - local_unit, |
4755 | - relation_set as _relation_set, |
4756 | - leader_get as _leader_get, |
4757 | - leader_set, |
4758 | - is_leader, |
4759 | -) |
4760 | - |
4761 | - |
4762 | -""" |
4763 | -This helper provides functions to support use of a peer relation |
4764 | -for basic key/value storage, with the added benefit that all storage |
4765 | -can be replicated across peer units. |
4766 | - |
4767 | -Requirement to use: |
4768 | - |
4769 | -To use this, the "peer_echo()" method has to be called form the peer |
4770 | -relation's relation-changed hook: |
4771 | - |
4772 | -@hooks.hook("cluster-relation-changed") # Adapt the to your peer relation name |
4773 | -def cluster_relation_changed(): |
4774 | - peer_echo() |
4775 | - |
4776 | -Once this is done, you can use peer storage from anywhere: |
4777 | - |
4778 | -@hooks.hook("some-hook") |
4779 | -def some_hook(): |
4780 | - # You can store and retrieve key/values this way: |
4781 | - if is_relation_made("cluster"): # from charmhelpers.core.hookenv |
4782 | - # There are peers available so we can work with peer storage |
4783 | - peer_store("mykey", "myvalue") |
4784 | - value = peer_retrieve("mykey") |
4785 | - print value |
4786 | - else: |
4787 | - print "No peers joind the relation, cannot share key/values :(" |
4788 | -""" |
4789 | - |
4790 | - |
4791 | -def leader_get(attribute=None, rid=None): |
4792 | - """Wrapper to ensure that settings are migrated from the peer relation. |
4793 | - |
4794 | - This is to support upgrading an environment that does not support |
4795 | - Juju leadership election to one that does. |
4796 | - |
4797 | - If a setting is not extant in the leader-get but is on the relation-get |
4798 | - peer rel, it is migrated and marked as such so that it is not re-migrated. |
4799 | - """ |
4800 | - migration_key = '__leader_get_migrated_settings__' |
4801 | - if not is_leader(): |
4802 | - return _leader_get(attribute=attribute) |
4803 | - |
4804 | - settings_migrated = False |
4805 | - leader_settings = _leader_get(attribute=attribute) |
4806 | - previously_migrated = _leader_get(attribute=migration_key) |
4807 | - |
4808 | - if previously_migrated: |
4809 | - migrated = set(json.loads(previously_migrated)) |
4810 | - else: |
4811 | - migrated = set([]) |
4812 | - |
4813 | - try: |
4814 | - if migration_key in leader_settings: |
4815 | - del leader_settings[migration_key] |
4816 | - except TypeError: |
4817 | - pass |
4818 | - |
4819 | - if attribute: |
4820 | - if attribute in migrated: |
4821 | - return leader_settings |
4822 | - |
4823 | - # If attribute not present in leader db, check if this unit has set |
4824 | - # the attribute in the peer relation |
4825 | - if not leader_settings: |
4826 | - peer_setting = _relation_get(attribute=attribute, unit=local_unit(), |
4827 | - rid=rid) |
4828 | - if peer_setting: |
4829 | - leader_set(settings={attribute: peer_setting}) |
4830 | - leader_settings = peer_setting |
4831 | - |
4832 | - if leader_settings: |
4833 | - settings_migrated = True |
4834 | - migrated.add(attribute) |
4835 | - else: |
4836 | - r_settings = _relation_get(unit=local_unit(), rid=rid) |
4837 | - if r_settings: |
4838 | - for key in set(r_settings.keys()).difference(migrated): |
4839 | - # Leader setting wins |
4840 | - if not leader_settings.get(key): |
4841 | - leader_settings[key] = r_settings[key] |
4842 | - |
4843 | - settings_migrated = True |
4844 | - migrated.add(key) |
4845 | - |
4846 | - if settings_migrated: |
4847 | - leader_set(**leader_settings) |
4848 | - |
4849 | - if migrated and settings_migrated: |
4850 | - migrated = json.dumps(list(migrated)) |
4851 | - leader_set(settings={migration_key: migrated}) |
4852 | - |
4853 | - return leader_settings |
4854 | - |
4855 | - |
4856 | -def relation_set(relation_id=None, relation_settings=None, **kwargs): |
4857 | - """Attempt to use leader-set if supported in the current version of Juju, |
4858 | - otherwise falls back on relation-set. |
4859 | - |
4860 | - Note that we only attempt to use leader-set if the provided relation_id is |
4861 | - a peer relation id or no relation id is provided (in which case we assume |
4862 | - we are within the peer relation context). |
4863 | - """ |
4864 | - try: |
4865 | - if relation_id in relation_ids('cluster'): |
4866 | - return leader_set(settings=relation_settings, **kwargs) |
4867 | - else: |
4868 | - raise NotImplementedError |
4869 | - except NotImplementedError: |
4870 | - return _relation_set(relation_id=relation_id, |
4871 | - relation_settings=relation_settings, **kwargs) |
4872 | - |
4873 | - |
4874 | -def relation_get(attribute=None, unit=None, rid=None): |
4875 | - """Attempt to use leader-get if supported in the current version of Juju, |
4876 | - otherwise falls back on relation-get. |
4877 | - |
4878 | - Note that we only attempt to use leader-get if the provided rid is a peer |
4879 | - relation id or no relation id is provided (in which case we assume we are |
4880 | - within the peer relation context). |
4881 | - """ |
4882 | - try: |
4883 | - if rid in relation_ids('cluster'): |
4884 | - return leader_get(attribute, rid) |
4885 | - else: |
4886 | - raise NotImplementedError |
4887 | - except NotImplementedError: |
4888 | - return _relation_get(attribute=attribute, rid=rid, unit=unit) |
4889 | - |
4890 | - |
4891 | -def peer_retrieve(key, relation_name='cluster'): |
4892 | - """Retrieve a named key from peer relation `relation_name`.""" |
4893 | - cluster_rels = relation_ids(relation_name) |
4894 | - if len(cluster_rels) > 0: |
4895 | - cluster_rid = cluster_rels[0] |
4896 | - return relation_get(attribute=key, rid=cluster_rid, |
4897 | - unit=local_unit()) |
4898 | - else: |
4899 | - raise ValueError('Unable to detect' |
4900 | - 'peer relation {}'.format(relation_name)) |
4901 | - |
4902 | - |
4903 | -def peer_retrieve_by_prefix(prefix, relation_name='cluster', delimiter='_', |
4904 | - inc_list=None, exc_list=None): |
4905 | - """ Retrieve k/v pairs given a prefix and filter using {inc,exc}_list """ |
4906 | - inc_list = inc_list if inc_list else [] |
4907 | - exc_list = exc_list if exc_list else [] |
4908 | - peerdb_settings = peer_retrieve('-', relation_name=relation_name) |
4909 | - matched = {} |
4910 | - if peerdb_settings is None: |
4911 | - return matched |
4912 | - for k, v in peerdb_settings.items(): |
4913 | - full_prefix = prefix + delimiter |
4914 | - if k.startswith(full_prefix): |
4915 | - new_key = k.replace(full_prefix, '') |
4916 | - if new_key in exc_list: |
4917 | - continue |
4918 | - if new_key in inc_list or len(inc_list) == 0: |
4919 | - matched[new_key] = v |
4920 | - return matched |
4921 | - |
4922 | - |
4923 | -def peer_store(key, value, relation_name='cluster'): |
4924 | - """Store the key/value pair on the named peer relation `relation_name`.""" |
4925 | - cluster_rels = relation_ids(relation_name) |
4926 | - if len(cluster_rels) > 0: |
4927 | - cluster_rid = cluster_rels[0] |
4928 | - relation_set(relation_id=cluster_rid, |
4929 | - relation_settings={key: value}) |
4930 | - else: |
4931 | - raise ValueError('Unable to detect ' |
4932 | - 'peer relation {}'.format(relation_name)) |
4933 | - |
4934 | - |
4935 | -def peer_echo(includes=None, force=False): |
4936 | - """Echo filtered attributes back onto the same relation for storage. |
4937 | - |
4938 | - This is a requirement to use the peerstorage module - it needs to be called |
4939 | - from the peer relation's changed hook. |
4940 | - |
4941 | - If Juju leader support exists this will be a noop unless force is True. |
4942 | - """ |
4943 | - try: |
4944 | - is_leader() |
4945 | - except NotImplementedError: |
4946 | - pass |
4947 | - else: |
4948 | - if not force: |
4949 | - return # NOOP if leader-election is supported |
4950 | - |
4951 | - # Use original non-leader calls |
4952 | - relation_get = _relation_get |
4953 | - relation_set = _relation_set |
4954 | - |
4955 | - rdata = relation_get() |
4956 | - echo_data = {} |
4957 | - if includes is None: |
4958 | - echo_data = rdata.copy() |
4959 | - for ex in ['private-address', 'public-address']: |
4960 | - if ex in echo_data: |
4961 | - echo_data.pop(ex) |
4962 | - else: |
4963 | - for attribute, value in six.iteritems(rdata): |
4964 | - for include in includes: |
4965 | - if include in attribute: |
4966 | - echo_data[attribute] = value |
4967 | - if len(echo_data) > 0: |
4968 | - relation_set(relation_settings=echo_data) |
4969 | - |
4970 | - |
4971 | -def peer_store_and_set(relation_id=None, peer_relation_name='cluster', |
4972 | - peer_store_fatal=False, relation_settings=None, |
4973 | - delimiter='_', **kwargs): |
4974 | - """Store passed-in arguments both in argument relation and in peer storage. |
4975 | - |
4976 | - It functions like doing relation_set() and peer_store() at the same time, |
4977 | - with the same data. |
4978 | - |
4979 | - @param relation_id: the id of the relation to store the data on. Defaults |
4980 | - to the current relation. |
4981 | - @param peer_store_fatal: Set to True, the function will raise an exception |
4982 | - should the peer sotrage not be avialable.""" |
4983 | - |
4984 | - relation_settings = relation_settings if relation_settings else {} |
4985 | - relation_set(relation_id=relation_id, |
4986 | - relation_settings=relation_settings, |
4987 | - **kwargs) |
4988 | - if is_relation_made(peer_relation_name): |
4989 | - for key, value in six.iteritems(dict(list(kwargs.items()) + |
4990 | - list(relation_settings.items()))): |
4991 | - key_prefix = relation_id or current_relation_id() |
4992 | - peer_store(key_prefix + delimiter + key, |
4993 | - value, |
4994 | - relation_name=peer_relation_name) |
4995 | - else: |
4996 | - if peer_store_fatal: |
4997 | - raise ValueError('Unable to detect ' |
4998 | - 'peer relation {}'.format(peer_relation_name)) |
4999 | |
5000 | === removed directory 'hooks/charmhelpers/contrib/saltstack' |
The diff has been truncated for viewing.