Add apparmor template for neutron services

Add support for application of apparmor profiles to
neutron and nova daemons that run on neutron-gateway
units.

By default this is disabled but may be enabled by setting
the aa-profile-mode option to ether 'complain' or 'enforce'.

Note that the apparmor profiles do not try to reproduce the
permissions required for all operations that may be undertaken
using oslo.rootwrap; daemons are granted permission to run
'sudo' without any apparmor based restrictions.

Change-Id: Ibe568a46ee4c1f1148c162f0f0b2907153770efe
This commit is contained in:
David Ames 2016-03-30 13:34:53 -07:00
parent d8943bba30
commit 83d0ad0238
24 changed files with 615 additions and 39 deletions

View File

@ -260,3 +260,9 @@ options:
The CPU core multiplier to use when configuring worker processes for The CPU core multiplier to use when configuring worker processes for
neutron and nova-metadata-api. By default, the number of workers for neutron and nova-metadata-api. By default, the number of workers for
each daemon is set to twice the number of CPU cores a service unit has. each daemon is set to twice the number of CPU cores a service unit has.
aa-profile-mode:
type: string
default: 'disable'
description: |
Experimental enable apparmor profile. Valid settings: 'complain', 'enforce' or 'disable'.
AA disabled by default.

View File

@ -14,6 +14,11 @@
import os import os
from charmhelpers.contrib.network.ip import (
get_address_in_network,
get_iface_addr,
is_ip,
)
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, log,
DEBUG, DEBUG,
@ -121,6 +126,36 @@ class SSHConfigContext(object):
return cipher[weak_ciphers] return cipher[weak_ciphers]
def get_listening(self, listen=['0.0.0.0']):
"""Returns a list of addresses SSH can list on
Turns input into a sensible list of IPs SSH can listen on. Input
must be a python list of interface names, IPs and/or CIDRs.
:param listen: list of IPs, CIDRs, interface names
:returns: list of IPs available on the host
"""
if listen == ['0.0.0.0']:
return listen
value = []
for network in listen:
try:
ip = get_address_in_network(network=network, fatal=True)
except ValueError:
if is_ip(network):
ip = network
else:
try:
ip = get_iface_addr(iface=network, fatal=False)[0]
except IndexError:
continue
value.append(ip)
if value == []:
return ['0.0.0.0']
return value
def __call__(self): def __call__(self):
settings = utils.get_settings('ssh') settings = utils.get_settings('ssh')
if settings['common']['network_ipv6_enable']: if settings['common']['network_ipv6_enable']:
@ -180,7 +215,7 @@ class SSHDConfigContext(SSHConfigContext):
addr_family = 'inet' addr_family = 'inet'
ctxt = { ctxt = {
'ssh_ip': settings['server']['listen_to'], 'ssh_ip': self.get_listening(settings['server']['listen_to']),
'password_auth_allowed': 'password_auth_allowed':
settings['server']['password_authentication'], settings['server']['password_authentication'],
'ports': settings['common']['ports'], 'ports': settings['common']['ports'],

View File

@ -406,7 +406,7 @@ def is_ip(address):
# Test to see if already an IPv4/IPv6 address # Test to see if already an IPv4/IPv6 address
address = netaddr.IPAddress(address) address = netaddr.IPAddress(address)
return True return True
except netaddr.AddrFormatError: except (netaddr.AddrFormatError, ValueError):
return False return False

View File

@ -258,6 +258,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
('vivid', 'kilo'), ('vivid', 'kilo'),
('wily', 'liberty'), ('wily', 'liberty'),
('xenial', 'mitaka'), ('xenial', 'mitaka'),
('yakkety', 'newton'),
]) ])
if self.openstack: if self.openstack:
os_origin = self.openstack.split(':')[1] os_origin = self.openstack.split(':')[1]

View File

@ -1421,9 +1421,9 @@ class InternalEndpointContext(OSContextGenerator):
class AppArmorContext(OSContextGenerator): class AppArmorContext(OSContextGenerator):
"""Base class for apparmor contexts.""" """Base class for apparmor contexts."""
def __init__(self): def __init__(self, profile_name=None):
self._ctxt = None self._ctxt = None
self.aa_profile = None self.aa_profile = profile_name
self.aa_utils_packages = ['apparmor-utils'] self.aa_utils_packages = ['apparmor-utils']
@property @property
@ -1442,6 +1442,8 @@ class AppArmorContext(OSContextGenerator):
if config('aa-profile-mode') in ['disable', 'enforce', 'complain']: if config('aa-profile-mode') in ['disable', 'enforce', 'complain']:
ctxt = {'aa_profile_mode': config('aa-profile-mode'), ctxt = {'aa_profile_mode': config('aa-profile-mode'),
'ubuntu_release': lsb_release()['DISTRIB_RELEASE']} 'ubuntu_release': lsb_release()['DISTRIB_RELEASE']}
if self.aa_profile:
ctxt['aa_profile'] = self.aa_profile
else: else:
ctxt = None ctxt = None
return ctxt return ctxt

View File

@ -30,6 +30,7 @@ from charmhelpers.contrib.hahelpers.cluster import is_clustered
PUBLIC = 'public' PUBLIC = 'public'
INTERNAL = 'int' INTERNAL = 'int'
ADMIN = 'admin' ADMIN = 'admin'
ACCESS = 'access'
ADDRESS_MAP = { ADDRESS_MAP = {
PUBLIC: { PUBLIC: {
@ -49,7 +50,13 @@ ADDRESS_MAP = {
'config': 'os-admin-network', 'config': 'os-admin-network',
'fallback': 'private-address', 'fallback': 'private-address',
'override': 'os-admin-hostname', 'override': 'os-admin-hostname',
} },
ACCESS: {
'binding': 'access',
'config': 'access-network',
'fallback': 'private-address',
'override': 'os-access-hostname',
},
} }

View File

@ -249,6 +249,8 @@ def neutron_plugins():
plugins['nsx']['server_packages'].remove('neutron-plugin-vmware') plugins['nsx']['server_packages'].remove('neutron-plugin-vmware')
plugins['nsx']['server_packages'].append('python-vmware-nsx') plugins['nsx']['server_packages'].append('python-vmware-nsx')
plugins['nsx']['config'] = '/etc/neutron/nsx.ini' plugins['nsx']['config'] = '/etc/neutron/nsx.ini'
plugins['vsp']['driver'] = (
'nuage_neutron.plugins.nuage.plugin.NuagePlugin')
return plugins return plugins

View File

@ -81,7 +81,12 @@ from charmhelpers.core.host import (
service_resume, service_resume,
restart_on_change_helper, restart_on_change_helper,
) )
from charmhelpers.fetch import apt_install, apt_cache, install_remote from charmhelpers.fetch import (
apt_install,
apt_cache,
install_remote,
get_upstream_version
)
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
from charmhelpers.contrib.openstack.exceptions import OSContextError from charmhelpers.contrib.openstack.exceptions import OSContextError
@ -1894,25 +1899,10 @@ def config_flags_parser(config_flags):
def os_application_version_set(package): def os_application_version_set(package):
'''Set version of application for Juju 2.0 and later''' '''Set version of application for Juju 2.0 and later'''
import apt_pkg as apt application_version = get_upstream_version(package)
cache = apt_cache()
application_version = None
application_codename = os_release(package)
try:
pkg = cache[package]
if not pkg.current_ver:
juju_log('Package {} is not currently installed.'.format(package),
DEBUG)
else:
application_version = apt.upstream_version(pkg.current_ver.ver_str)
except:
juju_log('Package {} has no installation candidate.'.format(package),
DEBUG)
# NOTE(jamespage) if not able to figure out package version, fallback to # NOTE(jamespage) if not able to figure out package version, fallback to
# openstack codename version detection. # openstack codename version detection.
if not application_version: if not application_version:
application_version_set(application_codename) application_version_set(os_release(package))
else: else:
application_version_set(application_version) application_version_set(application_version)

View File

@ -92,6 +92,7 @@ if __platform__ == "ubuntu":
apt_mark = fetch.apt_mark apt_mark = fetch.apt_mark
apt_hold = fetch.apt_hold apt_hold = fetch.apt_hold
apt_unhold = fetch.apt_unhold apt_unhold = fetch.apt_unhold
get_upstream_version = fetch.get_upstream_version
elif __platform__ == "centos": elif __platform__ == "centos":
yum_search = fetch.yum_search yum_search = fetch.yum_search

View File

@ -314,3 +314,23 @@ def _run_apt_command(cmd, fatal=False):
else: else:
subprocess.call(cmd, env=env) subprocess.call(cmd, env=env)
def get_upstream_version(package):
"""Determine upstream version based on installed package
@returns None (if not installed) or the upstream version
"""
import apt_pkg
cache = apt_cache()
try:
pkg = cache[package]
except:
# the package is unknown to the current apt cache.
return None
if not pkg.current_ver:
# package is known, but no version is currently installed.
return None
return apt_pkg.upstream_version(pkg.current_ver.ver_str)

View File

@ -47,11 +47,12 @@ def execd_submodule_paths(command, execd_dir=None):
yield path yield path
def execd_run(command, execd_dir=None, die_on_error=False, stderr=None): def execd_run(command, execd_dir=None, die_on_error=True, stderr=subprocess.STDOUT):
"""Run command for each module within execd_dir which defines it.""" """Run command for each module within execd_dir which defines it."""
for submodule_path in execd_submodule_paths(command, execd_dir): for submodule_path in execd_submodule_paths(command, execd_dir):
try: try:
subprocess.check_call(submodule_path, shell=True, stderr=stderr) subprocess.check_output(submodule_path, stderr=stderr,
universal_newlines=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
hookenv.log("Error ({}) running {}. Output: {}".format( hookenv.log("Error ({}) running {}. Output: {}".format(
e.returncode, e.cmd, e.output)) e.returncode, e.cmd, e.output))

View File

@ -14,7 +14,7 @@ from charmhelpers.fetch import (
from charmhelpers.contrib.openstack.context import ( from charmhelpers.contrib.openstack.context import (
OSContextGenerator, OSContextGenerator,
NeutronAPIContext, NeutronAPIContext,
config_flags_parser config_flags_parser,
) )
from charmhelpers.contrib.hahelpers.cluster import( from charmhelpers.contrib.hahelpers.cluster import(
eligible_leader eligible_leader

View File

@ -70,6 +70,7 @@ from neutron_utils import (
NEUTRON_COMMON, NEUTRON_COMMON,
assess_status, assess_status,
install_systemd_override, install_systemd_override,
configure_apparmor,
) )
hooks = Hooks() hooks = Hooks()
@ -142,6 +143,7 @@ def config_changed():
if valid_plugin(): if valid_plugin():
CONFIGS.write_all() CONFIGS.write_all()
configure_ovs() configure_ovs()
configure_apparmor()
else: else:
message = 'Please provide a valid plugin config' message = 'Please provide a valid plugin config'
log(message, level=ERROR) log(message, level=ERROR)

View File

@ -107,6 +107,39 @@ NEUTRON_PLUGIN_CONF = {
NSX: NEUTRON_NSX_PLUGIN_CONF, NSX: NEUTRON_NSX_PLUGIN_CONF,
} }
NEUTRON_DHCP_AA_PROFILE = 'usr.bin.neutron-dhcp-agent'
NEUTRON_L3_AA_PROFILE = 'usr.bin.neutron-l3-agent'
NEUTRON_LBAAS_AA_PROFILE = 'usr.bin.neutron-lbaas-agent'
NEUTRON_METADATA_AA_PROFILE = 'usr.bin.neutron-metadata-agent'
NEUTRON_METERING_AA_PROFILE = 'usr.bin.neutron-metering-agent'
NOVA_API_METADATA_AA_PROFILE = 'usr.bin.nova-api-metadata'
NEUTRON_OVS_AA_PROFILE = 'usr.bin.neutron-openvswitch-agent'
APPARMOR_PROFILES = [
NEUTRON_DHCP_AA_PROFILE,
NEUTRON_L3_AA_PROFILE,
NEUTRON_LBAAS_AA_PROFILE,
NEUTRON_METADATA_AA_PROFILE,
NEUTRON_METERING_AA_PROFILE,
NOVA_API_METADATA_AA_PROFILE,
NEUTRON_OVS_AA_PROFILE
]
NEUTRON_OVS_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NEUTRON_OVS_AA_PROFILE))
NEUTRON_DHCP_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NEUTRON_DHCP_AA_PROFILE))
NEUTRON_L3_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NEUTRON_L3_AA_PROFILE))
NEUTRON_LBAAS_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NEUTRON_LBAAS_AA_PROFILE))
NEUTRON_METADATA_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NEUTRON_METADATA_AA_PROFILE))
NEUTRON_METERING_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NEUTRON_METERING_AA_PROFILE))
NOVA_API_METADATA_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
''.format(NOVA_API_METADATA_AA_PROFILE))
GATEWAY_PKGS = { GATEWAY_PKGS = {
OVS: [ OVS: [
"neutron-plugin-openvswitch-agent", "neutron-plugin-openvswitch-agent",
@ -313,6 +346,12 @@ NOVA_CONFIG_FILES = {
context.NotificationDriverContext()], context.NotificationDriverContext()],
'services': ['nova-api-metadata'] 'services': ['nova-api-metadata']
}, },
NOVA_API_METADATA_AA_PROFILE_PATH: {
'services': ['nova-api-metadata'],
'hook_contexts': [
context.AppArmorContext(NOVA_API_METADATA_AA_PROFILE)
],
},
} }
NEUTRON_SHARED_CONFIG_FILES = { NEUTRON_SHARED_CONFIG_FILES = {
@ -330,6 +369,30 @@ NEUTRON_SHARED_CONFIG_FILES = {
NeutronGatewayContext()], NeutronGatewayContext()],
'services': ['neutron-metadata-agent'] 'services': ['neutron-metadata-agent']
}, },
NEUTRON_DHCP_AA_PROFILE_PATH: {
'services': ['neutron-dhcp-agent'],
'hook_contexts': [
context.AppArmorContext(NEUTRON_DHCP_AA_PROFILE)
],
},
NEUTRON_LBAAS_AA_PROFILE_PATH: {
'services': ['neutron-lbaas-agent'],
'hook_contexts': [
context.AppArmorContext(NEUTRON_LBAAS_AA_PROFILE)
],
},
NEUTRON_METADATA_AA_PROFILE_PATH: {
'services': ['neutron-metadata-agent'],
'hook_contexts': [
context.AppArmorContext(NEUTRON_METADATA_AA_PROFILE)
],
},
NEUTRON_METERING_AA_PROFILE_PATH: {
'services': ['neutron-metering-agent'],
'hook_contexts': [
context.AppArmorContext(NEUTRON_METERING_AA_PROFILE)
],
},
} }
NEUTRON_SHARED_CONFIG_FILES.update(NOVA_CONFIG_FILES) NEUTRON_SHARED_CONFIG_FILES.update(NOVA_CONFIG_FILES)
@ -377,13 +440,21 @@ NEUTRON_OVS_CONFIG_FILES = {
'hook_contexts': [NeutronGatewayContext()], 'hook_contexts': [NeutronGatewayContext()],
'services': ['neutron-plugin-openvswitch-agent'] 'services': ['neutron-plugin-openvswitch-agent']
}, },
NEUTRON_ML2_PLUGIN_CONF: { NEUTRON_OVS_AGENT_CONF: {
'hook_contexts': [NeutronGatewayContext()], 'hook_contexts': [NeutronGatewayContext()],
'services': ['neutron-plugin-openvswitch-agent'] 'services': ['neutron-plugin-openvswitch-agent']
}, },
NEUTRON_OVS_AGENT_CONF: { NEUTRON_OVS_AA_PROFILE_PATH: {
'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-openvswitch-agent'],
'services': ['neutron-openvswitch-agent'] 'hook_contexts': [
context.AppArmorContext(NEUTRON_OVS_AA_PROFILE)
],
},
NEUTRON_L3_AA_PROFILE_PATH: {
'services': ['neutron-l3-agent', 'neutron-vpn-agent'],
'hook_contexts': [
context.AppArmorContext(NEUTRON_L3_AA_PROFILE)
],
}, },
EXT_PORT_CONF: { EXT_PORT_CONF: {
'hook_contexts': [ExternalPortContext()], 'hook_contexts': [ExternalPortContext()],
@ -940,10 +1011,10 @@ def git_pre_install():
add_user_to_group('nova', 'nova') add_user_to_group('nova', 'nova')
for d in dirs: for d in dirs:
mkdir(d, owner='neutron', group='neutron', perms=0755, force=False) mkdir(d, owner='neutron', group='neutron', perms=0o755, force=False)
for l in logs: for l in logs:
write_file(l, '', owner='neutron', group='neutron', perms=0644) write_file(l, '', owner='neutron', group='neutron', perms=0o644)
def git_post_install(projects_yaml): def git_post_install(projects_yaml):
@ -1440,3 +1511,9 @@ def _pause_resume_helper(f, configs):
f(assess_status_func(configs), f(assess_status_func(configs),
services=active_services, services=active_services,
ports=None) ports=None)
def configure_apparmor():
'''Configure all apparmor profiles for the local unit'''
for profile in APPARMOR_PROFILES:
context.AppArmorContext(profile).setup_aa_profile()

View File

@ -0,0 +1,51 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/neutron-dhcp-agent {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/neutron-dhcp-agent r,
/sbin/ldconfig* rix,
/{,usr/}bin/ r,
/{,usr/}bin/** rix,
/etc/neutron/** r,
/var/lib/neutron/** rwk,
/var/log/neutron/** rwk,
/{,var/}run/neutron/** rwk,
/{,var/}run/lock/neutron/** rwk,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
/usr/sbin/dnsmasq rix,
# Allow ip to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -0,0 +1,49 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/neutron-l3-agent {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/neutron-l3-agent r,
/sbin/ldconfig* rix,
/{,usr/}bin/ r,
/{,usr/}bin/** rix,
/etc/neutron/** r,
/var/lib/neutron/** rwk,
/var/log/neutron/** rwk,
/{,var/}run/neutron/** rwk,
/{,var/}run/lock/neutron/** rwk,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
# Allow ip to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -0,0 +1,51 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/neutron-lbaas-agent {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/neutron-lbaas-agent r,
/sbin/ldconfig* rix,
/bin/ r,
/bin/** rix,
/usr/bin/ r,
/usr/bin/** rix,
/etc/neutron/** r,
/var/lib/neutron/** rwk,
/var/log/neutron/** rwk,
/{,var/}run/neutron/** rwk,
/{,var/}run/lock/neutron/** rwk,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
# Allow ip to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -0,0 +1,49 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/neutron-metadata-agent {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/neutron-metadata-agent r,
/sbin/ldconfig* rix,
/{,usr/}bin/ r,
/{,usr/}bin/** rix,
/etc/neutron/** r,
/var/lib/neutron/** rwk,
/var/log/neutron/** rwk,
/{,var/}run/neutron/** rwk,
/{,var/}run/lock/neutron/** rwk,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
# Allow ip to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -0,0 +1,49 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/neutron-metering-agent {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/neutron-metering-agent r,
/sbin/ldconfig* rix,
/{,usr/}bin/ r,
/{,usr/}bin/** rix,
/etc/neutron/** r,
/var/lib/neutron/** rwk,
/var/log/neutron/** rwk,
/{,var/}run/neutron/** rwk,
/{,var/}run/lock/neutron/** rwk,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
# Allow ip to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -0,0 +1,53 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/neutron-openvswitch-agent {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/neutron-openvswitch-agent r,
/sbin/ldconfig* rix,
/{,usr/}bin/ r,
/{,usr/}bin/** rix,
/etc/neutron/** r,
/etc/udev/udev.conf r,
/var/lib/neutron/** rwk,
/var/log/neutron/** rwk,
/{,var/}run/neutron/** rwk,
/{,var/}run/lock/neutron/** rwk,
/run/udev/* r,
/sys/kernel/uevent_seqnum r,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
# Allow ip and ps to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/{,s}bin/ps Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -0,0 +1,49 @@
# Last Modified: Fri Apr 1 16:26:34 2016
# Mode: {{aa_profile_mode}}
#include <tunables/global>
/usr/bin/nova-metadata-api {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
/usr/bin/nova-metadata-api r,
/sbin/ldconfig* rix,
/{,usr/}bin/ r,
/{,usr/}bin/** rix,
/etc/nova/** r,
/var/lib/nova/** rwk,
/var/log/nova/** rwk,
/{,var/}run/nova/** rwk,
/{,var/}run/lock/nova/** rwk,
# Allow unconfined sudo to support oslo.rootwrap
# profile makes no attempt to restrict this as this
# is limited by the appropriate rootwrap configuration.
/usr/bin/sudo Ux,
# Allow ip to run unrestricted for unpriviledged commands
/{,s}bin/ip Ux,
/tmp/* rw,
/var/tmp/* a,
# Required for parsing of managed process cmdline arguments
/proc/*/cmdline r,
# Required for assessment of current state of networking
/proc/sys/net/** r,
{% if ubuntu_release <= '12.04' %}
/proc/*/mounts r,
/proc/*/status r,
/proc/*/ns/net r,
{% else %}
owner @{PROC}/@{pid}/mounts r,
owner @{PROC}/@{pid}/status r,
owner @{PROC}/@{pid}/ns/net r,
{% endif %}
}

View File

@ -36,8 +36,8 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment):
self._deploy() self._deploy()
u.log.info('Waiting on extended status checks...') u.log.info('Waiting on extended status checks...')
exclude_services = ['mysql'] self.exclude_services = ['mysql']
self._auto_wait_for_status(exclude_services=exclude_services) self._auto_wait_for_status(exclude_services=self.exclude_services)
self._initialize_tests() self._initialize_tests()
@ -92,7 +92,7 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment):
def _configure_services(self): def _configure_services(self):
"""Configure all of the services.""" """Configure all of the services."""
neutron_gateway_config = {} neutron_gateway_config = {'aa-profile-mode': 'enforce'}
if self.git: if self.git:
amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY') amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY')
@ -539,7 +539,6 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment):
expected = { expected = {
'private-address': api_ip, 'private-address': api_ip,
'neutron-plugin': 'ovs', 'neutron-plugin': 'ovs',
'neutron-security-groups': "no",
'neutron-url': api_endpoint, 'neutron-url': api_endpoint,
} }
ret = u.validate_relation_data(unit, relation, expected) ret = u.validate_relation_data(unit, relation, expected)
@ -1063,3 +1062,50 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment):
assert u.wait_on_action(action_id), "Resume action failed." assert u.wait_on_action(action_id), "Resume action failed."
assert u.status_get(self.neutron_gateway_sentry)[0] == "active" assert u.status_get(self.neutron_gateway_sentry)[0] == "active"
u.log.debug('OK') u.log.debug('OK')
def test_920_change_aa_profile(self):
"""Test changing the Apparmor profile mode"""
# Services which are expected to restart upon config change,
# and corresponding config files affected by the change
services = {
'neutron-lbaas-agent':
'/etc/apparmor.d/usr.bin.neutron-lbaas-agent',
'neutron-metering-agent':
'/etc/apparmor.d/usr.bin.neutron-metering-agent',
'neutron-dhcp-agent': '/etc/apparmor.d/usr.bin.neutron-dhcp-agent',
'neutron-metadata-agent':
'/etc/apparmor.d/usr.bin.neutron-metadata-agent',
}
if self._get_openstack_release() >= self.xenial_mitaka:
services['neutron-l3-agent'] = (
'/etc/apparmor.d/usr.bin.neutron-l3-agent')
sentry = self.neutron_gateway_sentry
juju_service = 'neutron-gateway'
mtime = u.get_sentry_time(sentry)
set_default = {'aa-profile-mode': 'enforce'}
set_alternate = {'aa-profile-mode': 'complain'}
sleep_time = 60
# Change to complain mode
self.d.configure(juju_service, set_alternate)
self._auto_wait_for_status(exclude_services=self.exclude_services)
for s, conf_file in services.iteritems():
u.log.debug("Checking that service restarted: {}".format(s))
if not u.validate_service_config_changed(sentry, mtime, s,
conf_file,
sleep_time=sleep_time):
self.d.configure(juju_service, set_default)
msg = "service {} didn't restart after config change".format(s)
amulet.raise_status(amulet.FAIL, msg=msg)
sleep_time = 0
output, code = sentry.run('aa-status '
'--complaining')
u.log.info("Assert output of aa-status --complaining >= 3. Result: {} "
"Exit Code: {}".format(output, code))
assert int(output) >= 3

View File

@ -258,6 +258,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
('vivid', 'kilo'), ('vivid', 'kilo'),
('wily', 'liberty'), ('wily', 'liberty'),
('xenial', 'mitaka'), ('xenial', 'mitaka'),
('yakkety', 'newton'),
]) ])
if self.openstack: if self.openstack:
os_origin = self.openstack.split(':')[1] os_origin = self.openstack.split(':')[1]

View File

@ -41,9 +41,9 @@ TO_PATCH = [
'lsb_release', 'lsb_release',
'mkdir', 'mkdir',
'copy2', 'copy2',
'NeutronAPIContext',
'init_is_systemd', 'init_is_systemd',
'os_application_version_set', 'os_application_version_set',
'NeutronAPIContext',
] ]
openstack_origin_git = \ openstack_origin_git = \
@ -337,11 +337,11 @@ class TestNeutronUtils(CharmTestCase):
self.config.return_value = 'ovs' self.config.return_value = 'ovs'
self.get_os_codename_install_source.return_value = 'havana' self.get_os_codename_install_source.return_value = 'havana'
mock_get_packages.return_value = ['neutron-vpn-agent'] mock_get_packages.return_value = ['neutron-vpn-agent']
self.os_release.return_value = 'icehouse'
ex_map = { ex_map = {
neutron_utils.NEUTRON_CONF: ['neutron-dhcp-agent', neutron_utils.NEUTRON_CONF: ['neutron-dhcp-agent',
'neutron-metadata-agent', 'neutron-metadata-agent',
'neutron-plugin-openvswitch-agent', 'neutron-plugin-openvswitch-agent',
'neutron-plugin-metering-agent',
'neutron-metering-agent', 'neutron-metering-agent',
'neutron-lbaas-agent', 'neutron-lbaas-agent',
'neutron-vpn-agent'], 'neutron-vpn-agent'],
@ -358,10 +358,22 @@ class TestNeutronUtils(CharmTestCase):
neutron_utils.NEUTRON_DHCP_AGENT_CONF: ['neutron-dhcp-agent'], neutron_utils.NEUTRON_DHCP_AGENT_CONF: ['neutron-dhcp-agent'],
neutron_utils.NEUTRON_FWAAS_CONF: ['neutron-vpn-agent'], neutron_utils.NEUTRON_FWAAS_CONF: ['neutron-vpn-agent'],
neutron_utils.NEUTRON_METERING_AGENT_CONF: neutron_utils.NEUTRON_METERING_AGENT_CONF:
['neutron-metering-agent', 'neutron-plugin-metering-agent'], ['neutron-metering-agent'],
neutron_utils.NOVA_CONF: ['nova-api-metadata'], neutron_utils.NOVA_CONF: ['nova-api-metadata'],
neutron_utils.EXT_PORT_CONF: ['ext-port'], neutron_utils.EXT_PORT_CONF: ['ext-port'],
neutron_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'], neutron_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'],
neutron_utils.NEUTRON_DHCP_AA_PROFILE_PATH: ['neutron-dhcp-agent'],
neutron_utils.NEUTRON_OVS_AA_PROFILE_PATH:
['neutron-plugin-openvswitch-agent'],
neutron_utils.NEUTRON_L3_AA_PROFILE_PATH: ['neutron-vpn-agent'],
neutron_utils.NEUTRON_LBAAS_AA_PROFILE_PATH:
['neutron-lbaas-agent'],
neutron_utils.NEUTRON_METADATA_AA_PROFILE_PATH:
['neutron-metadata-agent'],
neutron_utils.NEUTRON_METERING_AA_PROFILE_PATH:
['neutron-metering-agent'],
neutron_utils.NOVA_API_METADATA_AA_PROFILE_PATH:
['nova-api-metadata'],
} }
self.assertDictEqual(neutron_utils.restart_map(), ex_map) self.assertDictEqual(neutron_utils.restart_map(), ex_map)
@ -394,6 +406,18 @@ class TestNeutronUtils(CharmTestCase):
neutron_utils.NOVA_CONF: ['nova-api-metadata'], neutron_utils.NOVA_CONF: ['nova-api-metadata'],
neutron_utils.EXT_PORT_CONF: ['ext-port'], neutron_utils.EXT_PORT_CONF: ['ext-port'],
neutron_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'], neutron_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'],
neutron_utils.NEUTRON_DHCP_AA_PROFILE_PATH: ['neutron-dhcp-agent'],
neutron_utils.NEUTRON_OVS_AA_PROFILE_PATH:
['neutron-openvswitch-agent'],
neutron_utils.NEUTRON_L3_AA_PROFILE_PATH: ['neutron-vpn-agent'],
neutron_utils.NEUTRON_LBAAS_AA_PROFILE_PATH:
['neutron-lbaas-agent'],
neutron_utils.NEUTRON_METADATA_AA_PROFILE_PATH:
['neutron-metadata-agent'],
neutron_utils.NEUTRON_METERING_AA_PROFILE_PATH:
['neutron-metering-agent'],
neutron_utils.NOVA_API_METADATA_AA_PROFILE_PATH:
['nova-api-metadata'],
} }
self.assertEqual(ex_map, neutron_utils.restart_map()) self.assertEqual(ex_map, neutron_utils.restart_map())
@ -462,6 +486,16 @@ class TestNeutronUtils(CharmTestCase):
neutron_utils.NOVA_CONF: ['nova-api-metadata'], neutron_utils.NOVA_CONF: ['nova-api-metadata'],
neutron_utils.EXT_PORT_CONF: ['ext-port'], neutron_utils.EXT_PORT_CONF: ['ext-port'],
neutron_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'], neutron_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'],
neutron_utils.NEUTRON_DHCP_AA_PROFILE_PATH: ['neutron-dhcp-agent'],
neutron_utils.NEUTRON_L3_AA_PROFILE_PATH: ['neutron-vpn-agent'],
neutron_utils.NEUTRON_LBAAS_AA_PROFILE_PATH:
['neutron-lbaas-agent'],
neutron_utils.NEUTRON_METADATA_AA_PROFILE_PATH:
['neutron-metadata-agent'],
neutron_utils.NEUTRON_METERING_AA_PROFILE_PATH:
['neutron-metering-agent'],
neutron_utils.NOVA_API_METADATA_AA_PROFILE_PATH:
['nova-api-metadata'],
} }
self.assertDictEqual(neutron_utils.restart_map(), ex_map) self.assertDictEqual(neutron_utils.restart_map(), ex_map)