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