Skip to content

Commit

Permalink
Add hardening support
Browse files Browse the repository at this point in the history
Add charmhelpers.contrib.hardening and calls to install,
config-changed, upgrade-charm and update-status hooks.
Also add new config option to allow one or more hardening
modules to be applied at runtime.

Change-Id: If0d1e10b58ed506e0aca659f30120b8d5c96c04f
  • Loading branch information
dosaboy committed Mar 24, 2016
1 parent 64394fa commit 6ab28b3
Show file tree
Hide file tree
Showing 69 changed files with 3,941 additions and 30 deletions.
1 change: 1 addition & 0 deletions charm-helpers-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ include:
- contrib.network.ip
- contrib.python.packages
- contrib.charmsupport
- contrib.hardening|inc=*
38 changes: 38 additions & 0 deletions charmhelpers/contrib/hardening/README.hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Juju charm-helpers hardening library

## Description

This library provides multiple implementations of system and application
hardening that conform to the standards of http://hardening.io/.

Current implementations include:

* OS
* SSH
* MySQL
* Apache

## Requirements

* Juju Charms

## Usage

1. Synchronise this library into your charm and add the harden() decorator
(from contrib.hardening.harden) to any functions or methods you want to use
to trigger hardening of your application/system.

2. Add a config option called 'harden' to your charm config.yaml and set it to
a space-delimited list of hardening modules you want to run e.g. "os ssh"

3. Override any config defaults (contrib.hardening.defaults) by adding a file
called hardening.yaml to your charm root containing the name(s) of the
modules whose settings you want override at root level and then any settings
with overrides e.g.

os:
general:
desktop_enable: True

4. Now just run your charm as usual and hardening will be applied each time the
hook runs.
15 changes: 15 additions & 0 deletions charmhelpers/contrib/hardening/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 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/>.
19 changes: 19 additions & 0 deletions charmhelpers/contrib/hardening/apache/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 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/>.

from os import path

TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates')
31 changes: 31 additions & 0 deletions charmhelpers/contrib/hardening/apache/checks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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/>.

from charmhelpers.core.hookenv import (
log,
DEBUG,
)
from charmhelpers.contrib.hardening.apache.checks import config


def run_apache_checks():
log("Starting Apache hardening checks.", level=DEBUG)
checks = config.get_audits()
for check in checks:
log("Running '%s' check" % (check.__class__.__name__), level=DEBUG)
check.ensure_compliance()

log("Apache hardening checks complete.", level=DEBUG)
100 changes: 100 additions & 0 deletions charmhelpers/contrib/hardening/apache/checks/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 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 os
import re
import subprocess


from charmhelpers.core.hookenv import (
log,
INFO,
)
from charmhelpers.contrib.hardening.audits.file import (
FilePermissionAudit,
DirectoryPermissionAudit,
NoReadWriteForOther,
TemplatedFile,
)
from charmhelpers.contrib.hardening.audits.apache import DisabledModuleAudit
from charmhelpers.contrib.hardening.apache import TEMPLATES_DIR
from charmhelpers.contrib.hardening import utils


def get_audits():
"""Get Apache hardening config audits.
:returns: dictionary of audits
"""
if subprocess.call(['which', 'apache2'], stdout=subprocess.PIPE) != 0:
log("Apache server does not appear to be installed on this node - "
"skipping apache hardening", level=INFO)
return []

context = ApacheConfContext()
settings = utils.get_settings('apache')
audits = [
FilePermissionAudit(paths='/etc/apache2/apache2.conf', user='root',
group='root', mode=0o0640),

TemplatedFile(os.path.join(settings['common']['apache_dir'],
'mods-available/alias.conf'),
context,
TEMPLATES_DIR,
mode=0o0755,
user='root',
service_actions=[{'service': 'apache2',
'actions': ['restart']}]),

TemplatedFile(os.path.join(settings['common']['apache_dir'],
'conf-enabled/hardening.conf'),
context,
TEMPLATES_DIR,
mode=0o0640,
user='root',
service_actions=[{'service': 'apache2',
'actions': ['restart']}]),

DirectoryPermissionAudit(settings['common']['apache_dir'],
user='root',
group='root',
mode=0o640),

DisabledModuleAudit(settings['hardening']['modules_to_disable']),

NoReadWriteForOther(settings['common']['apache_dir']),
]

return audits


class ApacheConfContext(object):
"""Defines the set of key/value pairs to set in a apache config file.
This context, when called, will return a dictionary containing the
key/value pairs of setting to specify in the
/etc/apache/conf-enabled/hardening.conf file.
"""
def __call__(self):
settings = utils.get_settings('apache')
ctxt = settings['hardening']

out = subprocess.check_output(['apache2', '-v'])
ctxt['apache_version'] = re.search(r'.+version: Apache/(.+?)\s.+',
out).group(1)
ctxt['apache_icondir'] = '/usr/share/apache2/icons/'
ctxt['traceenable'] = settings['hardening']['traceenable']
return ctxt
Empty file.
31 changes: 31 additions & 0 deletions charmhelpers/contrib/hardening/apache/templates/alias.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
###############################################################################
# WARNING: This configuration file is maintained by Juju. Local changes may
# be overwritten.
###############################################################################
<IfModule alias_module>
#
# Aliases: Add here as many aliases as you need (with no limit). The format is
# Alias fakename realname
#
# Note that if you include a trailing / on fakename then the server will
# require it to be present in the URL. So "/icons" isn't aliased in this
# example, only "/icons/". If the fakename is slash-terminated, then the
# realname must also be slash terminated, and if the fakename omits the
# trailing slash, the realname must also omit it.
#
# We include the /icons/ alias for FancyIndexed directory listings. If
# you do not use FancyIndexing, you may comment this out.
#
Alias /icons/ "{{ apache_icondir }}/"

<Directory "{{ apache_icondir }}">
Options -Indexes -MultiViews -FollowSymLinks
AllowOverride None
{% if apache_version == '2.4' -%}
Require all granted
{% else -%}
Order allow,deny
Allow from all
{% endif %}
</Directory>
</IfModule>
18 changes: 18 additions & 0 deletions charmhelpers/contrib/hardening/apache/templates/hardening.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
###############################################################################
# WARNING: This configuration file is maintained by Juju. Local changes may
# be overwritten.
###############################################################################

<Location / >
<LimitExcept {{ allowed_http_methods }} >
# http://httpd.apache.org/docs/2.4/upgrading.html
{% if apache_version > '2.2' -%}
Require all granted
{% else -%}
Order Allow,Deny
Deny from all
{% endif %}
</LimitExcept>
</Location>

TraceEnable {{ traceenable }}
63 changes: 63 additions & 0 deletions charmhelpers/contrib/hardening/audits/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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/>.


class BaseAudit(object): # NO-QA
"""Base class for hardening checks.
The lifecycle of a hardening check is to first check to see if the system
is in compliance for the specified check. If it is not in compliance, the
check method will return a value which will be supplied to the.
"""
def __init__(self, *args, **kwargs):
self.unless = kwargs.get('unless', None)
super(BaseAudit, self).__init__()

def ensure_compliance(self):
"""Checks to see if the current hardening check is in compliance or
not.
If the check that is performed is not in compliance, then an exception
should be raised.
"""
pass

def _take_action(self):
"""Determines whether to perform the action or not.
Checks whether or not an action should be taken. This is determined by
the truthy value for the unless parameter. If unless is a callback
method, it will be invoked with no parameters in order to determine
whether or not the action should be taken. Otherwise, the truthy value
of the unless attribute will determine if the action should be
performed.
"""
# Do the action if there isn't an unless override.
if self.unless is None:
return True

# Invoke the callback if there is one.
if hasattr(self.unless, '__call__'):
results = self.unless()
if results:
return False
else:
return True

if self.unless:
return False
else:
return True
Loading

0 comments on commit 6ab28b3

Please sign in to comment.