aboutsummaryrefslogtreecommitdiffstats
path: root/charms/trusty/ceilometer/charmhelpers/contrib/hardening/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'charms/trusty/ceilometer/charmhelpers/contrib/hardening/utils.py')
-rw-r--r--charms/trusty/ceilometer/charmhelpers/contrib/hardening/utils.py157
1 files changed, 157 insertions, 0 deletions
diff --git a/charms/trusty/ceilometer/charmhelpers/contrib/hardening/utils.py b/charms/trusty/ceilometer/charmhelpers/contrib/hardening/utils.py
new file mode 100644
index 0000000..a6743a4
--- /dev/null
+++ b/charms/trusty/ceilometer/charmhelpers/contrib/hardening/utils.py
@@ -0,0 +1,157 @@
+# Copyright 2016 Canonical Limited.
+#
+# This file is part of charm-helpers.
+#
+# charm-helpers is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# charm-helpers is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
+
+import glob
+import grp
+import os
+import pwd
+import six
+import yaml
+
+from charmhelpers.core.hookenv import (
+ log,
+ DEBUG,
+ INFO,
+ WARNING,
+ ERROR,
+)
+
+
+# Global settings cache. Since each hook fire entails a fresh module import it
+# is safe to hold this in memory and not risk missing config changes (since
+# they will result in a new hook fire and thus re-import).
+__SETTINGS__ = {}
+
+
+def _get_defaults(modules):
+ """Load the default config for the provided modules.
+
+ :param modules: stack modules config defaults to lookup.
+ :returns: modules default config dictionary.
+ """
+ default = os.path.join(os.path.dirname(__file__),
+ 'defaults/%s.yaml' % (modules))
+ return yaml.safe_load(open(default))
+
+
+def _get_schema(modules):
+ """Load the config schema for the provided modules.
+
+ NOTE: this schema is intended to have 1-1 relationship with they keys in
+ the default config and is used a means to verify valid overrides provided
+ by the user.
+
+ :param modules: stack modules config schema to lookup.
+ :returns: modules default schema dictionary.
+ """
+ schema = os.path.join(os.path.dirname(__file__),
+ 'defaults/%s.yaml.schema' % (modules))
+ return yaml.safe_load(open(schema))
+
+
+def _get_user_provided_overrides(modules):
+ """Load user-provided config overrides.
+
+ :param modules: stack modules to lookup in user overrides yaml file.
+ :returns: overrides dictionary.
+ """
+ overrides = os.path.join(os.environ['JUJU_CHARM_DIR'],
+ 'hardening.yaml')
+ if os.path.exists(overrides):
+ log("Found user-provided config overrides file '%s'" %
+ (overrides), level=DEBUG)
+ settings = yaml.safe_load(open(overrides))
+ if settings and settings.get(modules):
+ log("Applying '%s' overrides" % (modules), level=DEBUG)
+ return settings.get(modules)
+
+ log("No overrides found for '%s'" % (modules), level=DEBUG)
+ else:
+ log("No hardening config overrides file '%s' found in charm "
+ "root dir" % (overrides), level=DEBUG)
+
+ return {}
+
+
+def _apply_overrides(settings, overrides, schema):
+ """Get overrides config overlayed onto modules defaults.
+
+ :param modules: require stack modules config.
+ :returns: dictionary of modules config with user overrides applied.
+ """
+ if overrides:
+ for k, v in six.iteritems(overrides):
+ if k in schema:
+ if schema[k] is None:
+ settings[k] = v
+ elif type(schema[k]) is dict:
+ settings[k] = _apply_overrides(settings[k], overrides[k],
+ schema[k])
+ else:
+ raise Exception("Unexpected type found in schema '%s'" %
+ type(schema[k]), level=ERROR)
+ else:
+ log("Unknown override key '%s' - ignoring" % (k), level=INFO)
+
+ return settings
+
+
+def get_settings(modules):
+ global __SETTINGS__
+ if modules in __SETTINGS__:
+ return __SETTINGS__[modules]
+
+ schema = _get_schema(modules)
+ settings = _get_defaults(modules)
+ overrides = _get_user_provided_overrides(modules)
+ __SETTINGS__[modules] = _apply_overrides(settings, overrides, schema)
+ return __SETTINGS__[modules]
+
+
+def ensure_permissions(path, user, group, permissions, maxdepth=-1):
+ """Ensure permissions for path.
+
+ If path is a file, apply to file and return. If path is a directory,
+ apply recursively (if required) to directory contents and return.
+
+ :param user: user name
+ :param group: group name
+ :param permissions: octal permissions
+ :param maxdepth: maximum recursion depth. A negative maxdepth allows
+ infinite recursion and maxdepth=0 means no recursion.
+ :returns: None
+ """
+ if not os.path.exists(path):
+ log("File '%s' does not exist - cannot set permissions" % (path),
+ level=WARNING)
+ return
+
+ _user = pwd.getpwnam(user)
+ os.chown(path, _user.pw_uid, grp.getgrnam(group).gr_gid)
+ os.chmod(path, permissions)
+
+ if maxdepth == 0:
+ log("Max recursion depth reached - skipping further recursion",
+ level=DEBUG)
+ return
+ elif maxdepth > 0:
+ maxdepth -= 1
+
+ if os.path.isdir(path):
+ contents = glob.glob("%s/*" % (path))
+ for c in contents:
+ ensure_permissions(c, user=user, group=group,
+ permissions=permissions, maxdepth=maxdepth)