Apparmor profile for neutron-server

Add apparmor template for neutron-server
Charm helpers sync to bring in the AppArmorContext class
Create specific NeutronApiAppArmorContext
Add aa-profile-mode in config.yaml
Apply the apparmor profile as requested: disable, enforce, complain

Change-Id: I2c2843459beca349ad3ea4482bb9512b454b726d
This commit is contained in:
David Ames 2016-03-24 07:40:40 -07:00
parent 0d4e1bfa43
commit d2bb953fc0
8 changed files with 151 additions and 3 deletions

3
.gitignore vendored
View File

@ -5,3 +5,6 @@ bin
tags
*.sw[nop]
*.pyc
.unit-state.db
trusty/
xenial/

View File

@ -530,3 +530,9 @@ options:
description: |
The Midokura Enterprise MidoNet password credentials to access the
repository.
aa-profile-mode:
type: string
default: 'disable'
description: |
Enable apparmor profile. Valid settings: 'complain', 'enforce' or 'disable'.
AA disabled by default.

View File

@ -20,7 +20,7 @@ import os
import re
import time
from base64 import b64decode
from subprocess import check_call
from subprocess import check_call, CalledProcessError
import six
import yaml
@ -45,6 +45,7 @@ from charmhelpers.core.hookenv import (
INFO,
WARNING,
ERROR,
status_set,
)
from charmhelpers.core.sysctl import create as sysctl_create
@ -87,7 +88,7 @@ from charmhelpers.contrib.network.ip import (
is_address_in_network,
is_bridge_member,
)
from charmhelpers.contrib.openstack.utils import get_host_ip
from charmhelpers.contrib.openstack.utils import get_host_ip, os_release
from charmhelpers.core.unitdata import kv
try:
@ -1479,3 +1480,80 @@ class NetworkServiceContext(OSContextGenerator):
if self.context_complete(ctxt):
return ctxt
return {}
class AppArmorContext(OSContextGenerator):
"""Base class for apparmor contexts."""
def __init__(self):
self.ctxt = {}
self.aa_profile = None
self.aa_utils_packages = ['apparmor-utils']
def __call__(self):
"""
Validate aa-profile-mode settings is disable, enforce, or complain.
Otherwise return an empty dictionary.
:return ctxt: Dictionary of the apparmor profile mode or empty
dictionary.
"""
if config('aa-profile-mode') in ['disable', 'enforce', 'complain']:
self.ctxt = {'aa-profile-mode': config('aa-profile-mode')}
if os_release('neutron-common') >= 'mitaka':
self.ctxt.update({'python3': True})
return self.ctxt
def install_aa_utils(self):
"""
Install packages required for apparmor configuration.
"""
log("Installing apparmor utils.")
apt_install(packages=self.aa_utils_packages,
fatal=True)
def manually_disable_aa_profile(self):
"""
Manually disable an apparmor profile.
If aa-profile-mode is set to disabled (default) this is required as the
template has been written but apparmor is yet unaware of the profile
and aa-disable aa-profile fails. Without this the profile would kick
into enforce mode on the next service restart.
"""
profile_path = '/etc/apparmor.d'
disable_path = '/etc/apparmor.d/disable'
if not os.path.lexists(os.path.join(disable_path, self.aa_profile)):
os.symlink(os.path.join(profile_path, self.aa_profile),
os.path.join(disable_path, self.aa_profile))
def setup_aa_profile(self):
"""
Setup an apparmor profile.
The ctxt dictionary will contain the apparmor profile mode and
the apparmor profile name.
Makes calls out to aa-disable, aa-complain, or aa-enforce to setup
the apparmor profile.
"""
ctxt = self.__call__()
if not ctxt:
log("Not enabling apparmor Profile")
return
self.install_aa_utils()
cmd = ['aa-{}'.format(ctxt['aa-profile-mode'])]
cmd.append(ctxt['aa-profile'])
log("Setting up the apparmor profile for {} in {} mode."
"".format(ctxt['aa-profile'], ctxt['aa-profile-mode']))
try:
check_call(cmd)
except CalledProcessError as e:
if ctxt['aa-profile-mode'] == 'disable':
log("Manually disabling the apparmor profile for {}."
"".format(ctxt['aa-profile']))
self.manually_disable_aa_profile()
return
status_set('blocked', "Apparmor profile {} failed to be set to {}."
"".format(ctxt['aa-profile'],
ctxt['aa-profile-mode']))
raise e

View File

@ -14,10 +14,12 @@ from charmhelpers.contrib.openstack.utils import (
os_release,
)
VLAN = 'vlan'
VXLAN = 'vxlan'
GRE = 'gre'
OVERLAY_NET_TYPES = [VXLAN, GRE]
NEUTRON_API_AA_PROFILE = 'usr.bin.neutron-server'
def get_l2population():
@ -379,3 +381,17 @@ class MidonetContext(context.OSContextGenerator):
if self.context_complete(ctxt):
return ctxt
return {}
class NeutronAPIAppArmorContext(context.AppArmorContext):
def __init__(self):
super(NeutronAPIAppArmorContext, self).__init__()
self.aa_profile = NEUTRON_API_AA_PROFILE
def __call__(self):
self.ctxt = super(NeutronAPIAppArmorContext, self).__call__()
if not self.ctxt:
return self.ctxt
self.ctxt.update({'aa-profile': self.aa_profile})
return self.ctxt

View File

@ -80,6 +80,7 @@ from neutron_api_context import (
get_overlay_network_type,
IdentityServiceContext,
EtcdContext,
NeutronAPIAppArmorContext,
)
from charmhelpers.contrib.hahelpers.cluster import (
@ -289,6 +290,7 @@ def config_changed():
fatal=True)
configure_https()
update_nrpe_config()
NeutronAPIAppArmorContext().setup_aa_profile()
CONFIGS.write_all()
for r_id in relation_ids('neutron-api'):
neutron_api_relation_joined(rid=r_id)

View File

@ -135,6 +135,9 @@ APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
NEUTRON_DEFAULT = '/etc/default/neutron-server'
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
NEUTRON_API_AA_PROFILE = ('/etc/apparmor.d/{}'
''.format(neutron_api_context
.NEUTRON_API_AA_PROFILE))
BASE_RESOURCE_MAP = OrderedDict([
(NEUTRON_CONF, {
@ -172,6 +175,10 @@ BASE_RESOURCE_MAP = OrderedDict([
neutron_api_context.HAProxyContext()],
'services': ['haproxy'],
}),
(NEUTRON_API_AA_PROFILE, {
'services': ['neutron-server'],
'contexts': [neutron_api_context.NeutronAPIAppArmorContext()],
}),
])
# The interface is said to be satisfied if anyone of the interfaces in the

View File

@ -0,0 +1,32 @@
# Last Modified: Tue Mar 22 22:08:21 2016
#include <tunables/global>
/usr/bin/neutron-server {%if aa_profile_mode == 'complain' %}flags=(comlain){% endif %} {
#include <abstractions/apache2-common>
#include <abstractions/base>
#include <abstractions/python>
/bin/dash rix,
/bin/uname rix,
/etc/mime.types r,
/etc/neutron/* r,
/etc/neutron/plugins/ml2/* r,
/etc/neutron/policy.d/ r,
/proc/*/mounts r,
/proc/*/status r,
/sbin/ldconfig rix,
/sbin/ldconfig.real rix,
/tmp/* rw,
/tmp/*/ a,
/usr/bin/ r,
/usr/bin/neutron-server r,
{%if python3 %}
/usr/bin/python3.5 ix,
{% else %}
/usr/bin/python2.7 ix,
{% endif %}
/var/lib/neutron/* a,
/var/log/neutron/neutron-server.log w,
/var/tmp/* a,
}

View File

@ -216,6 +216,9 @@ class TestNeutronAPIUtils(CharmTestCase):
(nutils.HAPROXY_CONF, {
'services': ['haproxy'],
}),
(nutils.NEUTRON_API_AA_PROFILE, {
'services': ['neutron-server'],
}),
])
self.assertItemsEqual(_restart_map, expect)
@ -238,7 +241,8 @@ class TestNeutronAPIUtils(CharmTestCase):
'/etc/default/neutron-server',
'/etc/neutron/plugins/ml2/ml2_conf.ini',
'/etc/apache2/sites-available/openstack_https_frontend',
'/etc/haproxy/haproxy.cfg']
'/etc/haproxy/haproxy.cfg',
'/etc/apparmor.d/usr.bin.neutron-server']
self.assertItemsEqual(_regconfs.configs, confs)
@patch('os.path.isfile')