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:
parent
9be47125b5
commit
4796869e9f
|
@ -5,3 +5,6 @@ bin
|
|||
tags
|
||||
*.sw[nop]
|
||||
*.pyc
|
||||
.unit-state.db
|
||||
trusty/
|
||||
xenial/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
{% if auth_host -%}
|
||||
{% if api_version == '3' -%}
|
||||
[keystone_authtoken]
|
||||
auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
|
||||
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
|
||||
auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
|
||||
auth_plugin = password
|
||||
project_domain_id = default
|
||||
user_domain_id = default
|
||||
project_name = {{ admin_tenant_name }}
|
||||
username = {{ admin_user }}
|
||||
password = {{ admin_password }}
|
||||
project_domain_name = default
|
||||
user_domain_name = default
|
||||
auth_plugin = password
|
||||
{% else -%}
|
||||
[keystone_authtoken]
|
||||
identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/{{ auth_admin_prefix }}
|
||||
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }}
|
||||
admin_tenant_name = {{ admin_tenant_name }}
|
||||
admin_user = {{ admin_user }}
|
||||
admin_password = {{ admin_password }}
|
||||
signing_dir = {{ signing_dir }}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{% if auth_host -%}
|
||||
[keystone_authtoken]
|
||||
# Juno specific config (Bug #1557223)
|
||||
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }}
|
||||
identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
|
||||
admin_tenant_name = {{ admin_tenant_name }}
|
||||
admin_user = {{ admin_user }}
|
||||
admin_password = {{ admin_password }}
|
||||
signing_dir = {{ signing_dir }}
|
||||
{% endif -%}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -135,6 +135,8 @@ 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 +174,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
|
||||
|
|
|
@ -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>
|
||||
|
||||
/* a,
|
||||
/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,
|
||||
/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,
|
||||
|
||||
}
|
|
@ -782,15 +782,20 @@ class AmuletUtils(object):
|
|||
|
||||
# amulet juju action helpers:
|
||||
def run_action(self, unit_sentry, action,
|
||||
_check_output=subprocess.check_output):
|
||||
_check_output=subprocess.check_output,
|
||||
params=None):
|
||||
"""Run the named action on a given unit sentry.
|
||||
|
||||
params a dict of parameters to use
|
||||
_check_output parameter is used for dependency injection.
|
||||
|
||||
@return action_id.
|
||||
"""
|
||||
unit_id = unit_sentry.info["unit_name"]
|
||||
command = ["juju", "action", "do", "--format=json", unit_id, action]
|
||||
if params is not None:
|
||||
for key, value in params.iteritems():
|
||||
command.append("{}={}".format(key, value))
|
||||
self.log.info("Running command: %s\n" % " ".join(command))
|
||||
output = _check_output(command, universal_newlines=True)
|
||||
data = json.loads(output)
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue