Merge lp:~junaidali/charms/trusty/plumgrid-director/pg-restart into lp:~plumgrid-team/charms/trusty/plumgrid-director/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
Reviewer Review Type Date Requested Status
Bilal Baqar Pending
Review via email: mp+293101@code.launchpad.net
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.

Subscribers

People subscribed via source and target branches