PSARC/2016/531 OpenStack Neutron Security Groups
authorGirish Moodalbail <Girish.Moodalbail@oracle.COM>
Tue, 15 Nov 2016 18:00:12 -0800
changeset 7315 5cc40226273b
parent 7314 014a673c1f62
child 7316 7bdd5e4afd3d
PSARC/2016/531 OpenStack Neutron Security Groups 24691270 add support for OpenStack Neutron Security Groups
components/openstack/horizon/files/overrides.py
components/openstack/neutron/Makefile
components/openstack/neutron/files/agent/solaris/packetfilter.py
components/openstack/neutron/files/agent/solaris/pf_firewall.py
components/openstack/neutron/files/neutron-openvswitch-agent
components/openstack/neutron/files/neutron-openvswitch-agent.xml
components/openstack/neutron/neutron.p5m
components/openstack/neutron/patches/10-interface-driver-entry-point.patch
components/openstack/nova/patches/10-no-security-groups.patch
--- 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)
 
--- 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); \
--- 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:
--- /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
--- 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()
--- 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 @@
       </method_context>
     </exec_method>
     <exec_method timeout_seconds="60" type="method" name="stop"
-      exec=":kill"/>
+      exec="/lib/svc/method/neutron-openvswitch-agent %m %{restarter/contract}">
+      <method_context>
+	<method_credential user='neutron' group='neutron'/>
+      </method_context>
+    </exec_method>
 
     <instance name='default' enabled='false'>
       <!-- to start/stop/refresh the service -->
--- 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
--- 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
--- 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"))