# HG changeset patch # User Girish Moodalbail # Date 1479261612 28800 # Node ID 5cc40226273b03704fd78350c3fa65ac0df32ac2 # Parent 014a673c1f622541b0bb8ba2940d243b5ad0275c PSARC/2016/531 OpenStack Neutron Security Groups 24691270 add support for OpenStack Neutron Security Groups diff -r 014a673c1f62 -r 5cc40226273b components/openstack/horizon/files/overrides.py --- a/components/openstack/horizon/files/overrides.py Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/horizon/files/overrides.py Tue Nov 15 18:00:12 2016 -0800 @@ -28,39 +28,16 @@ LiveMigrateForm from openstack_dashboard.dashboards.admin.instances.tables import \ AdminInstancesTable, MigrateInstance -from openstack_dashboard.dashboards.project.access_and_security.tabs import \ - AccessAndSecurityTabs, APIAccessTab, FloatingIPsTab, KeypairsTab from openstack_dashboard.dashboards.project.images.images.tables import \ ImagesTable, CreateVolumeFromImage from openstack_dashboard.dashboards.project.instances import tables \ as project_tables from openstack_dashboard.dashboards.project.instances.tables import \ - InstancesTable, TogglePause, EditInstanceSecurityGroups + InstancesTable, TogglePause from openstack_dashboard.dashboards.project.instances.workflows import \ create_instance, update_instance -# Remove Security Groups from the LaunchInstance workflow - -class SolarisSetAccessControlsAction(create_instance.SetAccessControlsAction): - def __init__(self, request, *args, **kwargs): - super(SolarisSetAccessControlsAction, self).__init__(request, - *args, - **kwargs) - - del self.fields['groups'] - - class Meta(object): - name = _("Access & Security") - help_text = _("Control access to your instance via key pairs, " - "and other mechanisms.") - - def populate_groups_choices(self, request, context): - return [] - -create_instance.SetAccessControls.action_class = SolarisSetAccessControlsAction - - # Bootargs feature: # Add bootargs feature to 'SetAdvanced' workflow action. # Part of Project/Compute/Instances/Launch Instance @@ -168,12 +145,6 @@ contributes = ("bootargs", "bootargs_persist",) -# Remove 'UpdateInstanceSecurityGroups' from -# Project/Compute/Instances/Actions/Edit Instance -update_instance.UpdateInstance.default_steps = ( - update_instance.UpdateInstanceInfo, -) - # Bootargs feature: # if locally configured to do so add UpdateInstanceBootarg # to Project/Compute/Instances/Actions/Edit Instance @@ -182,9 +153,6 @@ UpdateInstanceBootarg, ) -# Remove 'SecurityGroupsTab' tab from Project/Compute/Access & Security -AccessAndSecurityTabs.tabs = (KeypairsTab, FloatingIPsTab, APIAccessTab) - # Remove 'TogglePause', 'MigrateInstance' actions from # Admin/System/Instances/Actions temp = list(AdminInstancesTable._meta.row_actions) @@ -192,10 +160,8 @@ temp.remove(TogglePause) AdminInstancesTable._meta.row_actions = tuple(temp) -# Remove 'EditInstanceSecurityGroups', 'TogglePause' actions from -# Project/Compute/Instances/Actions +# Remove 'TogglePause' action from Project/Compute/Instances/Actions temp = list(InstancesTable._meta.row_actions) -temp.remove(EditInstanceSecurityGroups) temp.remove(TogglePause) InstancesTable._meta.row_actions = tuple(temp) diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/Makefile --- a/components/openstack/neutron/Makefile Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/neutron/Makefile Tue Nov 15 18:00:12 2016 -0800 @@ -125,6 +125,7 @@ files/agent/solaris/net_lib.py \ files/agent/solaris/packetfilter.py \ files/agent/solaris/pd.py \ + files/agent/solaris/pf_firewall.py \ files/agent/solaris/ra.py \ $(PROTO_DIR)/$(PYTHON_LIB)/neutron/agent/solaris; \ $(MKDIR) $(PROTO_DIR)/$(PYTHON_LIB)/$(DEVICE_TEMPLATE); \ diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/files/agent/solaris/packetfilter.py --- a/components/openstack/neutron/files/agent/solaris/packetfilter.py Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/neutron/files/agent/solaris/packetfilter.py Tue Nov 15 18:00:12 2016 -0800 @@ -24,7 +24,7 @@ class PacketFilter(object): '''Wrapper around Solaris pfctl(1M) command''' - def __init__(self, anchor_name): + def __init__(self, anchor_name, layer2=False): '''All the PF rules/anchors will be placed under anchor_name. An anchor is a collection of rules, tables, and other anchors. They @@ -41,6 +41,14 @@ the methods in this class will operate under that root anchor. ''' self.root_anchor_path = anchor_name + self.layer2 = layer2 + + def _build_pfctl_cmd(self, options): + cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl'] + if self.layer2: + cmd.append('-2') + cmd.extend(options) + return cmd def get_anchor_path(self, subanchors): if subanchors: @@ -72,8 +80,7 @@ anchor_rule = anchor_rule + " " + anchor_option existing_anchor_rules.append(anchor_rule) process_input = '%s\n' % '\n'.join(sorted(existing_anchor_rules)) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-f', '-'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-f', '-']) utils.execute(cmd, process_input=process_input) def remove_nested_anchor_rule(self, parent_anchor, child_anchor): @@ -94,13 +101,12 @@ return existing_anchor_rules.remove(rule) process_input = '%s\n' % '\n'.join(existing_anchor_rules) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-f', '-'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-f', '-']) utils.execute(cmd, process_input=process_input) def list_anchor_rules(self, subanchors=None): anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, '-sr'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-sr']) try: stdout = utils.execute(cmd) except: @@ -109,7 +115,7 @@ def list_anchors(self, subanchors=None): anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, '-sA'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-sA']) try: stdout = utils.execute(cmd) except: @@ -118,29 +124,29 @@ def add_table(self, name, subanchors=None): anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-t', name, '-T', 'add'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-t', name, + '-T', 'add']) utils.execute(cmd) def add_table_entry(self, name, cidrs, subanchors=None): anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-t', name, '-T', 'add'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-t', name, + '-T', 'add']) cmd.extend(cidrs) utils.execute(cmd) def replace_table_entry(self, name, cidrs, subanchors=None): anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-t', name, '-T', 'replace'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-t', name, + '-T', 'replace']) cmd.extend(cidrs) utils.execute(cmd) def table_exists(self, name, subanchors=None): anchor_path = self.get_anchor_path(subanchors) try: - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-t', name, '-T', 'show'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-t', name, + '-T', 'show']) utils.execute(cmd) except: return False @@ -152,8 +158,8 @@ 'deleting') % name) return anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-t', name, '-T', 'delete'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-t', name, + '-T', 'delete']) utils.execute(cmd) def remove_table_entry(self, name, cidrs, subanchors=None): @@ -162,16 +168,15 @@ 'deleting') % name) return anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-t', name, '-T', 'delete'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-t', name, + '-T', 'delete']) cmd.extend(cidrs) utils.execute(cmd) def add_rules(self, rules, subanchors=None): anchor_path = self.get_anchor_path(subanchors) process_input = '\n'.join(rules) + '\n' - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-f', '-'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-f', '-']) utils.execute(cmd, process_input=process_input) def _get_rule_label(self, rule): @@ -188,7 +193,7 @@ # retrieve all the labels for rules, we will delete the state # after removing the rules - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, '-sr'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-sr']) try: stdout = utils.execute(cmd) except: @@ -204,14 +209,12 @@ labels.append(label) # delete the rules and tables - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, - '-F', 'all'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-F', 'all']) utils.execute(cmd) # clear the state for label in labels: - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-k', 'label', - '-k', label] + cmd = self._build_pfctl_cmd(['-k', 'label', '-k', label]) utils.execute(cmd) def _get_relative_nested_anchors(self, anchorname): @@ -225,7 +228,7 @@ def remove_anchor_recursively(self, subanchors=None, recurse_ctxt=False): anchor_path = self.get_anchor_path(subanchors) - cmd = ['/usr/bin/pfexec', '/usr/sbin/pfctl', '-a', anchor_path, '-sA'] + cmd = self._build_pfctl_cmd(['-a', anchor_path, '-sA']) try: stdout = utils.execute(cmd) except: diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/files/agent/solaris/pf_firewall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/openstack/neutron/files/agent/solaris/pf_firewall.py Tue Nov 15 18:00:12 2016 -0800 @@ -0,0 +1,438 @@ +# Copyright 2012, Nachi Ueno, NTT MCL, Inc. +# All Rights Reserved. +# +# Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections + +import netaddr +from oslo_config import cfg +from oslo_log import log as logging +import six + +from neutron._i18n import _LI +from neutron.agent.common import ovs_lib +from neutron.agent import firewall +from neutron.agent.solaris import packetfilter +from neutron.common import constants +from neutron.common import utils as c_utils + + +LOG = logging.getLogger(__name__) +ICMPV6_ALLOWED_UNSPEC_ADDR_TYPES = [131, 135, 143] + +DIRECTION_PF_PARAM = {firewall.INGRESS_DIRECTION: 'in', + firewall.EGRESS_DIRECTION: 'out'} + + +class PFBaseOVS(ovs_lib.BaseOVS): + def get_port_by_id(self, port_id): + ports = self.ovsdb.db_find( + 'Interface', ('external_ids', '=', {'iface-id': port_id}), + columns=['name']).execute() + if ports: + return ports[0]['name'] + return None + + +class PFFirewallDriver(firewall.FirewallDriver): + """Driver which enforces security groups through PF rules. + + Please look at neutron.agent.firewall.FirewallDriver for more information + on how the methods below are called from the Neutron Open vSwitch agent. It + all starts at prepare_port_filter() and then _setup_pf_rules() has all the + PF based logic to add correct rules on guest instance's port. + """ + + def __init__(self): + self.pf = packetfilter.PacketFilter("_auto/neutron:ovs:agent", + layer2=True) + # List of port which has security group + self.filtered_ports = {} + self.unfiltered_ports = {} + # List of security group rules for ports residing on this host + self.sg_rules = {} + # List of security group member ips for ports residing on this host + self.sg_members = collections.defaultdict( + lambda: collections.defaultdict(list)) + # Every PF rule needs to be labeled so that we can later kill the state + # associated with that rule (using pfctl -k label -k 110). It is hard + # to come up with a meaningfully named label for each PF rule, so we + # are resorting to numbers here. + self.label_num = 100 + self.portid_to_devname = {} + + def prepare_port_filter(self, port): + LOG.debug("Preparing device (%s) filter", port['device']) + self._setup_pf_rules(port) + + def apply_port_filter(self, port): + """We never call this method + + It exists here to override abstract method of parent abstract class. + """ + pass + + def update_port_filter(self, port): + LOG.debug("Updating device (%s) filter", port['device']) + self._setup_pf_rules(port, update=True) + + def remove_port_filter(self, port): + LOG.debug("Removing device (%s) filter", port['device']) + self.unfiltered_ports.pop(port['device'], None) + self.filtered_ports.pop(port['device'], None) + self._remove_rule_port_sec(port) + + @property + def ports(self): + return dict(self.filtered_ports, **self.unfiltered_ports) + + def update_security_group_rules(self, sg_id, sg_rules): + LOG.debug("Update rules of security group %s(%s)" % (sg_id, sg_rules)) + self.sg_rules[sg_id] = sg_rules + + def update_security_group_members(self, sg_id, sg_members): + LOG.debug("Update members of security group %s(%s)" % + (sg_id, sg_members)) + self.sg_members[sg_id] = collections.defaultdict(list, sg_members) + + def security_group_updated(self, action_type, sec_group_ids, + device_ids=None): + # TODO(gmoodalb): Extend this later to optimize handling of security + # groups update + pass + + def _get_label_number(self): + self.label_num += 1 + return self.label_num + + def _remove_rule_port_sec(self, port): + device_name = self.portid_to_devname.pop(port['id'], None) + if not device_name: + LOG.info(_LI("Could not find port: %s. Failed to remove PF rules " + "for that port"), port['id']) + return + LOG.debug("Removing PF rules for device_name(%s)" % device_name) + # we need to remove both ingress and egress + if '/' in device_name: + instance_name, datalink = device_name.split('/') + instance_name = instance_name.split(':')[1] + ingress = '%s_in' % datalink + egress = '%s_out' % datalink + else: + instance_name = device_name + ingress = 'in' + egress = 'out' + existing_anchor_rules = set(self.pf.list_anchor_rules([instance_name])) + existing_anchor_rules.discard('anchor "%s" in on %s all' % + (ingress, device_name)) + existing_anchor_rules.discard('anchor "%s" out on %s all' % + (egress, device_name)) + self.pf.add_rules(list(existing_anchor_rules), [instance_name]) + if existing_anchor_rules: + self.pf.remove_anchor_recursively([instance_name, ingress]) + self.pf.remove_anchor_recursively([instance_name, egress]) + else: + self.pf.remove_anchor_recursively([instance_name]) + + def _setup_pf_rules(self, port, update=False): + if not firewall.port_sec_enabled(port): + self.unfiltered_ports[port['device']] = port + self.filtered_ports.pop(port['device'], None) + self._remove_rule_port_sec(port) + else: + self.filtered_ports[port['device']] = port + self.unfiltered_ports.pop(port['device'], None) + if update: + self._remove_rule_port_sec(port) + self._add_rules_by_security_group(port, firewall.INGRESS_DIRECTION) + self._add_rules_by_security_group(port, firewall.EGRESS_DIRECTION) + + def _get_device_name(self, port): + bridge = PFBaseOVS() + device_name = bridge.get_port_by_id(port['id']) + if '/' in device_name: + device_name = 'dl:' + device_name + return device_name + + def _split_sgr_by_ethertype(self, security_group_rules): + ipv4_sg_rules = [] + ipv6_sg_rules = [] + for rule in security_group_rules: + if rule.get('ethertype') == constants.IPv4: + ipv4_sg_rules.append(rule) + elif rule.get('ethertype') == constants.IPv6: + if rule.get('protocol') in ['icmp', 'icmp6']: + rule['protocol'] = 'ipv6-icmp' + ipv6_sg_rules.append(rule) + return ipv4_sg_rules, ipv6_sg_rules + + def _select_sgr_by_direction(self, port, direction): + return [rule + for rule in port.get('security_group_rules', []) + if rule['direction'] == direction] + + def _spoofing_rule(self, port, device_name, ipv4_rules, ipv6_rules): + # Fixed rules for traffic sourced from unspecified addresses: 0.0.0.0 + # and :: + # Allow dhcp client discovery and request + ipv4_rules.append('pass out on %s proto udp from 0.0.0.0/32 port 68 ' + 'to 255.255.255.255/32 port 67 label "%s"' % + (device_name, self._get_label_number())) + + # Allow neighbor solicitation and multicast listener discovery + # from the unspecified address for duplicate address detection + for icmp6_type in ICMPV6_ALLOWED_UNSPEC_ADDR_TYPES: + ipv6_rules.append('pass out on %s inet6 proto ipv6-icmp ' + 'from ::/128 to ff02::/16 icmp6-type %s ' + 'label "%s"' % (device_name, icmp6_type, + self._get_label_number())) + + # Fixed rules for traffic after source address is verified + # Allow dhcp client renewal and rebinding + ipv4_rules.append('pass out on %s proto udp from port 68 to port 67 ' + 'label "%s"' % (device_name, + self._get_label_number())) + + # Drop Router Advts from the port. + ipv6_rules.append('block out quick on %s inet6 proto ipv6-icmp ' + 'icmp6-type %s label "%s"' % + (device_name, constants.ICMPV6_TYPE_RA, + self._get_label_number())) + # Allow IPv6 ICMP traffic + ipv6_rules.append('pass out on %s inet6 proto ipv6-icmp label "%s"' % + (device_name, self._get_label_number())) + # Allow IPv6 DHCP Client traffic + ipv6_rules.append('pass out on %s inet6 proto udp from port 546 ' + 'to port 547 label "%s"' % + (device_name, self._get_label_number())) + + def _drop_dhcp_rule(self, device_name, ipv4_rules, ipv6_rules): + # Drop dhcp packet from VM + ipv4_rules.append('block out quick on %s proto udp from port 67 ' + 'to port 68 label "%s"' % + (device_name, self._get_label_number())) + ipv6_rules.append('block out quick on %s inet6 proto udp ' + 'from port 547 to port 546 label "%s"' % + (device_name, self._get_label_number())) + + def _accept_inbound_icmpv6(self, device_name, ipv6_pf_rules): + # Allow multicast listener, neighbor solicitation and + # neighbor advertisement into the instance + for icmp6_type in constants.ICMPV6_ALLOWED_TYPES: + ipv6_pf_rules.append('pass in on %s inet6 proto ipv6-icmp ' + 'icmp6-type %s label "%s"' % + (device_name, icmp6_type, + self._get_label_number())) + + def _select_sg_rules_for_port(self, port, direction): + """Select rules from the security groups the port is member of.""" + port_sg_ids = port.get('security_groups', []) + port_rules = [] + + for sg_id in port_sg_ids: + for rule in self.sg_rules.get(sg_id, []): + if rule['direction'] == direction: + port_rules.extend( + self._expand_sg_rule_with_remote_ips( + rule, port, direction)) + return port_rules + + def _expand_sg_rule_with_remote_ips(self, rule, port, direction): + """Expand a remote group rule to rule per remote group IP.""" + remote_group_id = rule.get('remote_group_id') + if remote_group_id: + ethertype = rule['ethertype'] + port_ips = port.get('fixed_ips', []) + LOG.debug("Expanding rule: %s with remote IPs: %s" % + (rule, self.sg_members[remote_group_id][ethertype])) + for ip in self.sg_members[remote_group_id][ethertype]: + if ip not in port_ips: + ip_rule = rule.copy() + direction_ip_prefix = firewall.DIRECTION_IP_PREFIX[ + direction] + ip_prefix = str(netaddr.IPNetwork(ip).cidr) + ip_rule[direction_ip_prefix] = ip_prefix + yield ip_rule + else: + yield rule + + def _get_remote_sg_ids(self, port, direction=None): + sg_ids = port.get('security_groups', []) + remote_sg_ids = {constants.IPv4: set(), constants.IPv6: set()} + for sg_id in sg_ids: + for rule in self.sg_rules.get(sg_id, []): + if not direction or rule['direction'] == direction: + remote_sg_id = rule.get('remote_group_id') + ether_type = rule.get('ethertype') + if remote_sg_id and ether_type: + remote_sg_ids[ether_type].add(remote_sg_id) + return remote_sg_ids + + def _add_pf_rules(self, port, device_name, direction, ipv4_pf_rules, + ipv6_pf_rules): + if '/' in device_name: + instance_name, datalink = device_name.split('/') + instance_name = instance_name.split(':')[1] + else: + instance_name, datalink = (device_name, "") + self.pf.add_nested_anchor_rule(None, instance_name, None) + if direction == firewall.INGRESS_DIRECTION: + subanchor = '%s%s' % (datalink, '_in' if datalink else 'in') + new_anchor_rule = ['anchor "%s" in on %s all' % (subanchor, + device_name)] + else: + subanchor = '%s%s' % (datalink, '_out' if datalink else 'out') + new_anchor_rule = ['anchor "%s" out on %s all' % (subanchor, + device_name)] + existing_anchor_rules = self.pf.list_anchor_rules([instance_name]) + final_anchor_rules = set(existing_anchor_rules) | set(new_anchor_rule) + self.pf.add_rules(list(final_anchor_rules), [instance_name]) + + # self.pf.add_nested_anchor_rule(None, anchor_name, anchor_option) + self.pf.add_rules(ipv4_pf_rules + ipv6_pf_rules, + [instance_name, subanchor]) + self.portid_to_devname[port['id']] = device_name + + def _add_block_everything(self, device_name, direction, ipv4_pf_rules, + ipv6_pf_rules): + ''' Add a generic block everything rule. The default security group + in OpenStack adds 'pass all egress traffic' and prevents all the + incoming traffic''' + ipv4_pf_rules.append('block %s on %s label "%s"' % + (DIRECTION_PF_PARAM[direction], device_name, + self._get_label_number())) + ipv6_pf_rules.append('block %s on %s inet6 label "%s"' % + (DIRECTION_PF_PARAM[direction], device_name, + self._get_label_number())) + + def _add_rules_by_security_group(self, port, direction): + LOG.debug("Adding rules for Port: %s", port) + + device_name = self._get_device_name(port) + if not device_name: + LOG.info(_LI("Could not find port: %s on the OVS bridge. Failed " + "to add PF rules for that port"), port['id']) + return + # select rules for current port and direction + security_group_rules = self._select_sgr_by_direction(port, direction) + security_group_rules += self._select_sg_rules_for_port(port, direction) + # split groups by ip version + # for ipv4, 'pass' will be used + # for ipv6, 'pass inet6' will be used + ipv4_sg_rules, ipv6_sg_rules = self._split_sgr_by_ethertype( + security_group_rules) + ipv4_pf_rules = [] + ipv6_pf_rules = [] + self._add_block_everything(device_name, direction, ipv4_pf_rules, + ipv6_pf_rules) + # include fixed egress/ingress rules + if direction == firewall.EGRESS_DIRECTION: + self._add_fixed_egress_rules(port, device_name, ipv4_pf_rules, + ipv6_pf_rules) + elif direction == firewall.INGRESS_DIRECTION: + self._accept_inbound_icmpv6(device_name, ipv6_pf_rules) + # include IPv4 and IPv6 iptable rules from security group + LOG.debug("Converting %s IPv4 SG rules: %s" % + (direction, ipv4_sg_rules)) + ipv4_pf_rules += self._convert_sgr_to_pfr(device_name, direction, + ipv4_sg_rules) + LOG.debug("... to %s IPv4 PF rules: %s" % (direction, ipv4_pf_rules)) + LOG.debug("Converting %s IPv6 SG rules: %s" % + (direction, ipv6_sg_rules)) + ipv6_pf_rules += self._convert_sgr_to_pfr(device_name, direction, + ipv6_sg_rules) + LOG.debug("... to %s IPv6 PF rules: %s" % (direction, ipv6_pf_rules)) + + self._add_pf_rules(port, device_name, direction, ipv4_pf_rules, + ipv6_pf_rules) + + def _add_fixed_egress_rules(self, port, device_name, ipv4_pf_rules, + ipv6_pf_rules): + self._spoofing_rule(port, device_name, ipv4_pf_rules, ipv6_pf_rules) + self._drop_dhcp_rule(device_name, ipv4_pf_rules, ipv6_pf_rules) + + def _protocol_param(self, protocol, pf_rule): + if protocol: + pf_rule.append('proto %s' % protocol) + + def _port_param(self, protocol, port_range_min, port_range_max, pf_rule): + if port_range_min is None: + return + if protocol in ('tcp', 'udp'): + if port_range_min == port_range_max: + pf_rule.append('port %s' % port_range_min) + else: + pf_rule.append('port %s:%s' % (port_range_min, + port_range_max)) + elif protocol in ('icmp', 'ipv6-icmp'): + icmp_type = 'icmp-type' if protocol == 'icmp' else 'icmp6-type' + pf_rule.append('%s %s' % (icmp_type, port_range_min)) + if port_range_max is not None: + pf_rule.append('code %s' % port_range_max) + + def _ip_prefix_param(self, direction, ip_prefix, pf_rule): + if ip_prefix != 'any': + if '/' not in ip_prefix: + # we need to convert it into a cidr + ip_prefix = c_utils.ip_to_cidr(ip_prefix) + elif ip_prefix.endswith('/0'): + ip_prefix = 'any' + direction = 'from' if direction == firewall.INGRESS_DIRECTION else 'to' + pf_rule.append('%s %s' % (direction, ip_prefix)) + + def _ip_prefix_port_param(self, direction, sg_rule, pf_rule): + protocol = sg_rule.get('protocol') + if direction == firewall.INGRESS_DIRECTION: + ip_prefix = sg_rule.get('source_ip_prefix') + ip_prefix = ip_prefix if ip_prefix else 'any' + self._ip_prefix_param(direction, ip_prefix, pf_rule) + self._port_param(protocol, + sg_rule.get('source_port_range_min'), + sg_rule.get('source_port_range_max'), pf_rule) + self._ip_prefix_param(firewall.EGRESS_DIRECTION, 'any', pf_rule) + self._port_param(protocol, + sg_rule.get('port_range_min'), + sg_rule.get('port_range_max'), pf_rule) + else: + self._ip_prefix_param(firewall.INGRESS_DIRECTION, 'any', pf_rule) + self._port_param(protocol, + sg_rule.get('source_port_range_min'), + sg_rule.get('source_port_range_max'), pf_rule) + + ip_prefix = sg_rule.get('dest_ip_prefix') + ip_prefix = ip_prefix if ip_prefix else 'any' + self._ip_prefix_param(direction, ip_prefix, pf_rule) + self._port_param(protocol, + sg_rule.get('port_range_min'), + sg_rule.get('port_range_max'), pf_rule) + + def _convert_sgr_to_pfr(self, device_name, direction, + security_group_rules): + pf_rules = [] + for sg_rule in security_group_rules: + pf_rule = ['pass'] + pf_rule.append("%s on %s" % (DIRECTION_PF_PARAM[direction], + device_name)) + if sg_rule.get('ethertype') == constants.IPv6: + pf_rule.append('inet6') + else: + pf_rule.append('inet') + protocol = sg_rule.get('protocol') + self._protocol_param(protocol, pf_rule) + self._ip_prefix_port_param(direction, sg_rule, pf_rule) + pf_rule.append('label "%s"' % self._get_label_number()) + pf_rules.append(' '.join(pf_rule)) + return pf_rules diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/files/neutron-openvswitch-agent --- a/components/openstack/neutron/files/neutron-openvswitch-agent Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/neutron/files/neutron-openvswitch-agent Tue Nov 15 18:00:12 2016 -0800 @@ -15,10 +15,13 @@ # under the License. import os +from subprocess import CalledProcessError, check_call import sys import smf_include +from neutron.agent.solaris import packetfilter + def start(): # verify paths are valid @@ -27,10 +30,30 @@ print '%s does not exist or is not readable' % f return smf_include.SMF_EXIT_ERR_CONFIG + # Cleanup any security groups related PF rules + pf = packetfilter.PacketFilter('_auto/neutron:ovs:agent') + pf.remove_anchor_recursively() + cmd = "/usr/bin/pfexec /usr/lib/neutron/neutron-openvswitch-agent " \ "--config-file %s --config-file %s" % tuple(sys.argv[2:4]) smf_include.smf_subprocess(cmd) + +def stop(): + try: + # first kill the SMF contract + check_call(["/usr/bin/pkill", "-c", sys.argv[2]]) + except CalledProcessError as err: + print "failed to kill the SMF contract: %s" % err + return smf_include.SMF_EXIT_ERR_FATAL + + # We need to remove the PF rules added under _auto/neutron:ovs:agent + # anchor. + pf = packetfilter.PacketFilter('_auto/neutron:ovs:agent') + pf.remove_anchor_recursively() + return smf_include.SMF_EXIT_OK + + if __name__ == "__main__": os.putenv("LC_ALL", "C") smf_include.smf_main() diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/files/neutron-openvswitch-agent.xml --- a/components/openstack/neutron/files/neutron-openvswitch-agent.xml Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/neutron/files/neutron-openvswitch-agent.xml Tue Nov 15 18:00:12 2016 -0800 @@ -61,7 +61,11 @@ + exec="/lib/svc/method/neutron-openvswitch-agent %m %{restarter/contract}"> + + + + diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/neutron.p5m --- a/components/openstack/neutron/neutron.p5m Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/neutron/neutron.p5m Tue Nov 15 18:00:12 2016 -0800 @@ -223,6 +223,7 @@ file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/solaris/net_lib.py file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/solaris/packetfilter.py file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/solaris/pd.py +file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/solaris/pf_firewall.py file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/solaris/ra.py file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/windows/__init__.py file path=usr/lib/python$(PYVER)/vendor-packages/neutron/agent/windows/ip_lib.py diff -r 014a673c1f62 -r 5cc40226273b components/openstack/neutron/patches/10-interface-driver-entry-point.patch --- a/components/openstack/neutron/patches/10-interface-driver-entry-point.patch Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/neutron/patches/10-interface-driver-entry-point.patch Tue Nov 15 18:00:12 2016 -0800 @@ -1,22 +1,21 @@ -The Solaris OVS Interface driver needs to be added to the list of entry points -in order to be discovered and loaded by stevedore. +The Solaris OVS Interface driver and Solaris PF security group driver need to +be added to the list of entry points in order to be discovered and loaded by +stevedore. -*** neutron-8.0.0/setup.cfg 2016-04-07 00:47:18.000000000 -0700 ---- new/setup.cfg 2016-05-24 13:31:12.688314864 -0700 -*************** -*** 155,161 **** - ivs = neutron.agent.linux.interface:IVSInterfaceDriver - linuxbridge = neutron.agent.linux.interface:BridgeInterfaceDriver - null = neutron.agent.linux.interface:NullDriver -! openvswitch = neutron.agent.linux.interface:OVSInterfaceDriver - neutron.agent.firewall_drivers = - noop = neutron.agent.firewall:NoopFirewallDriver - iptables = neutron.agent.linux.iptables_firewall:IptablesFirewallDriver ---- 155,161 ---- - ivs = neutron.agent.linux.interface:IVSInterfaceDriver - linuxbridge = neutron.agent.linux.interface:BridgeInterfaceDriver - null = neutron.agent.linux.interface:NullDriver -! openvswitch = neutron.agent.solaris.interface:OVSInterfaceDriver - neutron.agent.firewall_drivers = - noop = neutron.agent.firewall:NoopFirewallDriver - iptables = neutron.agent.linux.iptables_firewall:IptablesFirewallDriver +--- neutron-8.1.2/setup.cfg 2016-06-09 18:50:09.000000000 -0700 ++++ new/setup.cfg 2016-09-08 22:28:20.271867703 -0700 +@@ -155,12 +155,13 @@ + ivs = neutron.agent.linux.interface:IVSInterfaceDriver + linuxbridge = neutron.agent.linux.interface:BridgeInterfaceDriver + null = neutron.agent.linux.interface:NullDriver +- openvswitch = neutron.agent.linux.interface:OVSInterfaceDriver ++ openvswitch = neutron.agent.solaris.interface:OVSInterfaceDriver + neutron.agent.firewall_drivers = + noop = neutron.agent.firewall:NoopFirewallDriver + iptables = neutron.agent.linux.iptables_firewall:IptablesFirewallDriver + iptables_hybrid = neutron.agent.linux.iptables_firewall:OVSHybridIptablesFirewallDriver + openvswitch = neutron.agent.linux.openvswitch_firewall:OVSFirewallDriver ++ pf = neutron.agent.solaris.pf_firewall:PFFirewallDriver + + [build_sphinx] + all_files = 1 diff -r 014a673c1f62 -r 5cc40226273b components/openstack/nova/patches/10-no-security-groups.patch --- a/components/openstack/nova/patches/10-no-security-groups.patch Tue Nov 15 16:50:44 2016 -0800 +++ b/components/openstack/nova/patches/10-no-security-groups.patch Tue Nov 15 18:00:12 2016 -0800 @@ -1,15 +1,78 @@ -In-house patch as Solaris doesn't currently support security groups. +Nova while spawning the instance expects the security group feature to be +enabled. When not enabled we get 404 Not Found error and this causes the +spawning of instances to fail. In the case of 404 Not Found error, we just +need to return an empty security group list. This is an issue with upstream, +and the patch must be proposed upstream. ---- nova-13.1.0/nova/network/neutronv2/api.py.~1~ 2016-06-14 08:45:49.000000000 -0700 -+++ nova-13.1.0/nova/network/neutronv2/api.py 2016-07-06 18:08:27.484252690 -0700 -@@ -606,8 +606,8 @@ class API(base_api.NetworkAPI): - self._check_external_network_attach(context, nets) - - security_groups = kwargs.get('security_groups', []) -- security_group_ids = self._process_security_groups( -- instance, neutron, security_groups) -+ # TODO(gmoodalb): Solaris doesn't currently support security groups. -+ security_group_ids = [] - - preexisting_port_ids = [] - created_port_ids = [] +*** nova-13.1.0/nova/network/neutronv2/api.py 2016-06-14 08:45:49.000000000 -0700 +--- new/nova/network/neutronv2/api.py 2016-10-31 20:37:36.416614641 -0700 +*************** +*** 483,490 **** + # group if len(security_groups) == 1 + if len(security_groups): + search_opts = {'tenant_id': instance.project_id} +! user_security_groups = neutron.list_security_groups( +! **search_opts).get('security_groups') + + for security_group in security_groups: + name_match = None +--- 483,496 ---- + # group if len(security_groups) == 1 + if len(security_groups): + search_opts = {'tenant_id': instance.project_id} +! try: +! user_security_groups = neutron.list_security_groups( +! **search_opts).get('security_groups') +! except neutron_client_exc.NotFound: +! # An admin could have disabled security group feature for the +! # cloud, and in that case the API above will end up in 404 not +! # found, so we need to return an empty list. +! return [] + + for security_group in security_groups: + name_match = None +*** nova-13.1.0/nova/api/openstack/compute/security_groups.py 2016-06-14 08:45:49.000000000 -0700 +--- new/nova/api/openstack/compute/security_groups.py 2016-11-01 11:21:01.453929563 -0700 +*************** +*** 172,178 **** + list(sorted(result, + key=lambda k: (k['tenant_id'], k['name'])))} + +! @extensions.expected_errors((400, 403)) + def create(self, req, body): + """Creates a new security group.""" + context = _authorize_context(req) +--- 172,178 ---- + list(sorted(result, + key=lambda k: (k['tenant_id'], k['name'])))} + +! @extensions.expected_errors((400, 403, 501)) + def create(self, req, body): + """Creates a new security group.""" + context = _authorize_context(req) +*** nova-13.1.0/nova/network/security_group/neutron_driver.py 2016-06-14 08:45:49.000000000 -0700 +--- new/nova/network/security_group/neutron_driver.py 2016-11-10 13:38:32.968864075 -0800 +*************** +*** 50,55 **** +--- 50,59 ---- + try: + security_group = neutron.create_security_group( + body).get('security_group') ++ except n_exc.NotFound: ++ raise exc.HTTPNotImplemented( ++ explanation='Neutron Security Groups feature is not available ' ++ 'on this cloud.') + except n_exc.BadRequest as e: + raise exception.Invalid(six.text_type(e)) + except n_exc.NeutronClientException as e: +*************** +*** 188,193 **** +--- 192,199 ---- + try: + security_groups = neutron.list_security_groups(**params).get( + 'security_groups') ++ except n_exc.NotFound: ++ security_groups = [] + except n_exc.NeutronClientException: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Neutron Error getting security groups"))