From 83d0ad02388765c04e3014217fe230dc5090f90a Mon Sep 17 00:00:00 2001 From: David Ames Date: Wed, 30 Mar 2016 13:34:53 -0700 Subject: [PATCH] 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 --- config.yaml | 6 ++ .../contrib/hardening/ssh/checks/config.py | 37 +++++++- hooks/charmhelpers/contrib/network/ip.py | 2 +- .../contrib/openstack/amulet/deployment.py | 1 + .../charmhelpers/contrib/openstack/context.py | 6 +- hooks/charmhelpers/contrib/openstack/ip.py | 9 +- .../charmhelpers/contrib/openstack/neutron.py | 2 + hooks/charmhelpers/contrib/openstack/utils.py | 26 ++---- hooks/charmhelpers/fetch/__init__.py | 1 + hooks/charmhelpers/fetch/ubuntu.py | 20 +++++ hooks/charmhelpers/payload/execd.py | 5 +- hooks/neutron_contexts.py | 2 +- hooks/neutron_hooks.py | 2 + hooks/neutron_utils.py | 89 +++++++++++++++++-- templates/usr.bin.neutron-dhcp-agent | 51 +++++++++++ templates/usr.bin.neutron-l3-agent | 49 ++++++++++ templates/usr.bin.neutron-lbaas-agent | 51 +++++++++++ templates/usr.bin.neutron-metadata-agent | 49 ++++++++++ templates/usr.bin.neutron-metering-agent | 49 ++++++++++ templates/usr.bin.neutron-openvswitch-agent | 53 +++++++++++ templates/usr.bin.nova-api-metadata | 49 ++++++++++ tests/basic_deployment.py | 54 ++++++++++- .../contrib/openstack/amulet/deployment.py | 1 + unit_tests/test_neutron_utils.py | 40 ++++++++- 24 files changed, 615 insertions(+), 39 deletions(-) create mode 100644 templates/usr.bin.neutron-dhcp-agent create mode 100644 templates/usr.bin.neutron-l3-agent create mode 100644 templates/usr.bin.neutron-lbaas-agent create mode 100644 templates/usr.bin.neutron-metadata-agent create mode 100644 templates/usr.bin.neutron-metering-agent create mode 100644 templates/usr.bin.neutron-openvswitch-agent create mode 100644 templates/usr.bin.nova-api-metadata diff --git a/config.yaml b/config.yaml index 23274c9f..c2acc268 100644 --- a/config.yaml +++ b/config.yaml @@ -260,3 +260,9 @@ options: The CPU core multiplier to use when configuring worker processes 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. + aa-profile-mode: + type: string + default: 'disable' + description: | + Experimental enable apparmor profile. Valid settings: 'complain', 'enforce' or 'disable'. + AA disabled by default. diff --git a/hooks/charmhelpers/contrib/hardening/ssh/checks/config.py b/hooks/charmhelpers/contrib/hardening/ssh/checks/config.py index 94e524e2..f3cac6d9 100644 --- a/hooks/charmhelpers/contrib/hardening/ssh/checks/config.py +++ b/hooks/charmhelpers/contrib/hardening/ssh/checks/config.py @@ -14,6 +14,11 @@ import os +from charmhelpers.contrib.network.ip import ( + get_address_in_network, + get_iface_addr, + is_ip, +) from charmhelpers.core.hookenv import ( log, DEBUG, @@ -121,6 +126,36 @@ class SSHConfigContext(object): 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): settings = utils.get_settings('ssh') if settings['common']['network_ipv6_enable']: @@ -180,7 +215,7 @@ class SSHDConfigContext(SSHConfigContext): addr_family = 'inet' ctxt = { - 'ssh_ip': settings['server']['listen_to'], + 'ssh_ip': self.get_listening(settings['server']['listen_to']), 'password_auth_allowed': settings['server']['password_authentication'], 'ports': settings['common']['ports'], diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index d6dee17c..2d2026e4 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -406,7 +406,7 @@ def is_ip(address): # Test to see if already an IPv4/IPv6 address address = netaddr.IPAddress(address) return True - except netaddr.AddrFormatError: + except (netaddr.AddrFormatError, ValueError): return False diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py index eb2566a5..d1d52137 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py @@ -258,6 +258,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): ('vivid', 'kilo'), ('wily', 'liberty'), ('xenial', 'mitaka'), + ('yakkety', 'newton'), ]) if self.openstack: os_origin = self.openstack.split(':')[1] diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 76737f22..b601a226 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -1421,9 +1421,9 @@ class InternalEndpointContext(OSContextGenerator): class AppArmorContext(OSContextGenerator): """Base class for apparmor contexts.""" - def __init__(self): + def __init__(self, profile_name=None): self._ctxt = None - self.aa_profile = None + self.aa_profile = profile_name self.aa_utils_packages = ['apparmor-utils'] @property @@ -1442,6 +1442,8 @@ class AppArmorContext(OSContextGenerator): if config('aa-profile-mode') in ['disable', 'enforce', 'complain']: ctxt = {'aa_profile_mode': config('aa-profile-mode'), 'ubuntu_release': lsb_release()['DISTRIB_RELEASE']} + if self.aa_profile: + ctxt['aa_profile'] = self.aa_profile else: ctxt = None return ctxt diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/hooks/charmhelpers/contrib/openstack/ip.py index 0fd3ac25..d1476b1a 100644 --- a/hooks/charmhelpers/contrib/openstack/ip.py +++ b/hooks/charmhelpers/contrib/openstack/ip.py @@ -30,6 +30,7 @@ from charmhelpers.contrib.hahelpers.cluster import is_clustered PUBLIC = 'public' INTERNAL = 'int' ADMIN = 'admin' +ACCESS = 'access' ADDRESS_MAP = { PUBLIC: { @@ -49,7 +50,13 @@ ADDRESS_MAP = { 'config': 'os-admin-network', 'fallback': 'private-address', 'override': 'os-admin-hostname', - } + }, + ACCESS: { + 'binding': 'access', + 'config': 'access-network', + 'fallback': 'private-address', + 'override': 'os-access-hostname', + }, } diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index d1510dd3..08c86fa7 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -249,6 +249,8 @@ def neutron_plugins(): plugins['nsx']['server_packages'].remove('neutron-plugin-vmware') plugins['nsx']['server_packages'].append('python-vmware-nsx') plugins['nsx']['config'] = '/etc/neutron/nsx.ini' + plugins['vsp']['driver'] = ( + 'nuage_neutron.plugins.nuage.plugin.NuagePlugin') return plugins diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 6cfa30fb..e1cc7687 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -81,7 +81,12 @@ from charmhelpers.core.host import ( service_resume, 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.loopback import ensure_loopback_device from charmhelpers.contrib.openstack.exceptions import OSContextError @@ -1894,25 +1899,10 @@ def config_flags_parser(config_flags): def os_application_version_set(package): '''Set version of application for Juju 2.0 and later''' - import apt_pkg as apt - 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) - + application_version = get_upstream_version(package) # NOTE(jamespage) if not able to figure out package version, fallback to # openstack codename version detection. if not application_version: - application_version_set(application_codename) + application_version_set(os_release(package)) else: application_version_set(application_version) diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index 174c6330..ec5e0fe9 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -92,6 +92,7 @@ if __platform__ == "ubuntu": apt_mark = fetch.apt_mark apt_hold = fetch.apt_hold apt_unhold = fetch.apt_unhold + get_upstream_version = fetch.get_upstream_version elif __platform__ == "centos": yum_search = fetch.yum_search diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py index 077869e1..fce496b2 100644 --- a/hooks/charmhelpers/fetch/ubuntu.py +++ b/hooks/charmhelpers/fetch/ubuntu.py @@ -314,3 +314,23 @@ def _run_apt_command(cmd, fatal=False): else: 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) diff --git a/hooks/charmhelpers/payload/execd.py b/hooks/charmhelpers/payload/execd.py index 0c42090f..1502aa0b 100644 --- a/hooks/charmhelpers/payload/execd.py +++ b/hooks/charmhelpers/payload/execd.py @@ -47,11 +47,12 @@ def execd_submodule_paths(command, execd_dir=None): 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.""" for submodule_path in execd_submodule_paths(command, execd_dir): 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: hookenv.log("Error ({}) running {}. Output: {}".format( e.returncode, e.cmd, e.output)) diff --git a/hooks/neutron_contexts.py b/hooks/neutron_contexts.py index ee567316..599e97f6 100644 --- a/hooks/neutron_contexts.py +++ b/hooks/neutron_contexts.py @@ -14,7 +14,7 @@ from charmhelpers.fetch import ( from charmhelpers.contrib.openstack.context import ( OSContextGenerator, NeutronAPIContext, - config_flags_parser + config_flags_parser, ) from charmhelpers.contrib.hahelpers.cluster import( eligible_leader diff --git a/hooks/neutron_hooks.py b/hooks/neutron_hooks.py index cf7feda2..1f4e96c5 100755 --- a/hooks/neutron_hooks.py +++ b/hooks/neutron_hooks.py @@ -70,6 +70,7 @@ from neutron_utils import ( NEUTRON_COMMON, assess_status, install_systemd_override, + configure_apparmor, ) hooks = Hooks() @@ -142,6 +143,7 @@ def config_changed(): if valid_plugin(): CONFIGS.write_all() configure_ovs() + configure_apparmor() else: message = 'Please provide a valid plugin config' log(message, level=ERROR) diff --git a/hooks/neutron_utils.py b/hooks/neutron_utils.py index cb2f0e59..e5e1c9b0 100644 --- a/hooks/neutron_utils.py +++ b/hooks/neutron_utils.py @@ -107,6 +107,39 @@ NEUTRON_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 = { OVS: [ "neutron-plugin-openvswitch-agent", @@ -313,6 +346,12 @@ NOVA_CONFIG_FILES = { context.NotificationDriverContext()], '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 = { @@ -330,6 +369,30 @@ NEUTRON_SHARED_CONFIG_FILES = { NeutronGatewayContext()], '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) @@ -377,13 +440,21 @@ NEUTRON_OVS_CONFIG_FILES = { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-openvswitch-agent'] }, - NEUTRON_ML2_PLUGIN_CONF: { + NEUTRON_OVS_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-openvswitch-agent'] }, - NEUTRON_OVS_AGENT_CONF: { - 'hook_contexts': [NeutronGatewayContext()], - 'services': ['neutron-openvswitch-agent'] + NEUTRON_OVS_AA_PROFILE_PATH: { + 'services': ['neutron-plugin-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: { 'hook_contexts': [ExternalPortContext()], @@ -940,10 +1011,10 @@ def git_pre_install(): add_user_to_group('nova', 'nova') 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: - write_file(l, '', owner='neutron', group='neutron', perms=0644) + write_file(l, '', owner='neutron', group='neutron', perms=0o644) def git_post_install(projects_yaml): @@ -1440,3 +1511,9 @@ def _pause_resume_helper(f, configs): f(assess_status_func(configs), services=active_services, ports=None) + + +def configure_apparmor(): + '''Configure all apparmor profiles for the local unit''' + for profile in APPARMOR_PROFILES: + context.AppArmorContext(profile).setup_aa_profile() diff --git a/templates/usr.bin.neutron-dhcp-agent b/templates/usr.bin.neutron-dhcp-agent new file mode 100644 index 00000000..6706575a --- /dev/null +++ b/templates/usr.bin.neutron-dhcp-agent @@ -0,0 +1,51 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/neutron-dhcp-agent { + #include + #include + #include + + /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 %} +} diff --git a/templates/usr.bin.neutron-l3-agent b/templates/usr.bin.neutron-l3-agent new file mode 100644 index 00000000..3e612de4 --- /dev/null +++ b/templates/usr.bin.neutron-l3-agent @@ -0,0 +1,49 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/neutron-l3-agent { + #include + #include + #include + + /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 %} +} diff --git a/templates/usr.bin.neutron-lbaas-agent b/templates/usr.bin.neutron-lbaas-agent new file mode 100644 index 00000000..881252df --- /dev/null +++ b/templates/usr.bin.neutron-lbaas-agent @@ -0,0 +1,51 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/neutron-lbaas-agent { + #include + #include + #include + + /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 %} +} diff --git a/templates/usr.bin.neutron-metadata-agent b/templates/usr.bin.neutron-metadata-agent new file mode 100644 index 00000000..34def84c --- /dev/null +++ b/templates/usr.bin.neutron-metadata-agent @@ -0,0 +1,49 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/neutron-metadata-agent { + #include + #include + #include + + /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 %} +} diff --git a/templates/usr.bin.neutron-metering-agent b/templates/usr.bin.neutron-metering-agent new file mode 100644 index 00000000..3d23c865 --- /dev/null +++ b/templates/usr.bin.neutron-metering-agent @@ -0,0 +1,49 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/neutron-metering-agent { + #include + #include + #include + + /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 %} +} diff --git a/templates/usr.bin.neutron-openvswitch-agent b/templates/usr.bin.neutron-openvswitch-agent new file mode 100644 index 00000000..303b8f52 --- /dev/null +++ b/templates/usr.bin.neutron-openvswitch-agent @@ -0,0 +1,53 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/neutron-openvswitch-agent { + #include + #include + #include + + /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 %} +} diff --git a/templates/usr.bin.nova-api-metadata b/templates/usr.bin.nova-api-metadata new file mode 100644 index 00000000..ae6be018 --- /dev/null +++ b/templates/usr.bin.nova-api-metadata @@ -0,0 +1,49 @@ +# Last Modified: Fri Apr 1 16:26:34 2016 +# Mode: {{aa_profile_mode}} +#include + +/usr/bin/nova-metadata-api { + #include + #include + #include + + /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 %} +} diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index abe29d34..39e84632 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -36,8 +36,8 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment): self._deploy() u.log.info('Waiting on extended status checks...') - exclude_services = ['mysql'] - self._auto_wait_for_status(exclude_services=exclude_services) + self.exclude_services = ['mysql'] + self._auto_wait_for_status(exclude_services=self.exclude_services) self._initialize_tests() @@ -92,7 +92,7 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment): def _configure_services(self): """Configure all of the services.""" - neutron_gateway_config = {} + neutron_gateway_config = {'aa-profile-mode': 'enforce'} if self.git: amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY') @@ -539,7 +539,6 @@ class NeutronGatewayBasicDeployment(OpenStackAmuletDeployment): expected = { 'private-address': api_ip, 'neutron-plugin': 'ovs', - 'neutron-security-groups': "no", 'neutron-url': api_endpoint, } 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.status_get(self.neutron_gateway_sentry)[0] == "active" 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 diff --git a/tests/charmhelpers/contrib/openstack/amulet/deployment.py b/tests/charmhelpers/contrib/openstack/amulet/deployment.py index eb2566a5..d1d52137 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/tests/charmhelpers/contrib/openstack/amulet/deployment.py @@ -258,6 +258,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): ('vivid', 'kilo'), ('wily', 'liberty'), ('xenial', 'mitaka'), + ('yakkety', 'newton'), ]) if self.openstack: os_origin = self.openstack.split(':')[1] diff --git a/unit_tests/test_neutron_utils.py b/unit_tests/test_neutron_utils.py index 511569c9..c6c8b01d 100644 --- a/unit_tests/test_neutron_utils.py +++ b/unit_tests/test_neutron_utils.py @@ -41,9 +41,9 @@ TO_PATCH = [ 'lsb_release', 'mkdir', 'copy2', - 'NeutronAPIContext', 'init_is_systemd', 'os_application_version_set', + 'NeutronAPIContext', ] openstack_origin_git = \ @@ -337,11 +337,11 @@ class TestNeutronUtils(CharmTestCase): self.config.return_value = 'ovs' self.get_os_codename_install_source.return_value = 'havana' mock_get_packages.return_value = ['neutron-vpn-agent'] + self.os_release.return_value = 'icehouse' ex_map = { neutron_utils.NEUTRON_CONF: ['neutron-dhcp-agent', 'neutron-metadata-agent', 'neutron-plugin-openvswitch-agent', - 'neutron-plugin-metering-agent', 'neutron-metering-agent', 'neutron-lbaas-agent', 'neutron-vpn-agent'], @@ -358,10 +358,22 @@ class TestNeutronUtils(CharmTestCase): neutron_utils.NEUTRON_DHCP_AGENT_CONF: ['neutron-dhcp-agent'], neutron_utils.NEUTRON_FWAAS_CONF: ['neutron-vpn-agent'], 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.EXT_PORT_CONF: ['ext-port'], 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) @@ -394,6 +406,18 @@ class TestNeutronUtils(CharmTestCase): neutron_utils.NOVA_CONF: ['nova-api-metadata'], neutron_utils.EXT_PORT_CONF: ['ext-port'], 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()) @@ -462,6 +486,16 @@ class TestNeutronUtils(CharmTestCase): neutron_utils.NOVA_CONF: ['nova-api-metadata'], neutron_utils.EXT_PORT_CONF: ['ext-port'], 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)