¡@

Home 

OpenStack Study: firewall.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright 2011 United States Government as represented by the

# Administrator of the National Aeronautics and Space Administration.

# All Rights Reserved.

# Copyright (c) 2011 Citrix Systems, Inc.

#

# 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.

from oslo.config import cfg

from nova.compute import utils as compute_utils

from nova import context

from nova.network import linux_net

from nova.objects import instance as instance_obj

from nova.objects import security_group as security_group_obj

from nova.objects import security_group_rule as security_group_rule_obj

from nova.openstack.common.gettextutils import _

from nova.openstack.common import importutils

from nova.openstack.common import log as logging

from nova import utils

from nova.virt import netutils

LOG = logging.getLogger(__name__)

firewall_opts = [

cfg.StrOpt('firewall_driver',

help='Firewall driver '

'(defaults to hypervisor specific iptables driver)'),

cfg.BoolOpt('allow_same_net_traffic',

default=True,

help='Whether to allow network traffic from same network'),

]

CONF = cfg.CONF

CONF.register_opts(firewall_opts)

CONF.import_opt('use_ipv6', 'nova.netconf')

**** CubicPower OpenStack Study ****

def load_driver(default, *args, **kwargs):

    fw_class = importutils.import_class(CONF.firewall_driver or default)

    return fw_class(*args, **kwargs)

**** CubicPower OpenStack Study ****

class FirewallDriver(object):

"""Firewall Driver base class.

Defines methods that any driver providing security groups

and provider firewall functionality should implement.

"""

**** CubicPower OpenStack Study ****

    def __init__(self, virtapi):

        self._virtapi = virtapi

**** CubicPower OpenStack Study ****

    def prepare_instance_filter(self, instance, network_info):

        """Prepare filters for the instance.

        At this point, the instance isn't running yet.

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def filter_defer_apply_on(self):

        """Defer application of IPTables rules."""

        pass

**** CubicPower OpenStack Study ****

    def filter_defer_apply_off(self):

        """Turn off deferral of IPTables rules and apply the rules now."""

        pass

**** CubicPower OpenStack Study ****

    def unfilter_instance(self, instance, network_info):

        """Stop filtering instance."""

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def apply_instance_filter(self, instance, network_info):

        """Apply instance filter.

        Once this method returns, the instance should be firewalled

        appropriately. This method should as far as possible be a

        no-op. It's vastly preferred to get everything set up in

        prepare_instance_filter.

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def refresh_security_group_rules(self, security_group_id):

        """Refresh security group rules from data store

        Gets called when a rule has been added to or removed from

        the security group.

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def refresh_security_group_members(self, security_group_id):

        """Refresh security group members from data store

        Gets called when an instance gets added to or removed from

        the security group.

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def refresh_instance_security_rules(self, instance):

        """Refresh security group rules from data store

        Gets called when an instance gets added to or removed from

        the security group the instance is a member of or if the

        group gains or looses a rule.

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def refresh_provider_fw_rules(self):

        """Refresh common rules for all hosts/instances from data store.

        Gets called when a rule has been added to or removed from

        the list of rules (via admin api).

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def setup_basic_filtering(self, instance, network_info):

        """Create rules to block spoofing and allow dhcp.

        This gets called when spawning an instance, before

        :py:meth:`prepare_instance_filter`.

        """

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

    def instance_filter_exists(self, instance, network_info):

        """Check nova-instance-instance-xxx exists."""

        raise NotImplementedError()

**** CubicPower OpenStack Study ****

class IptablesFirewallDriver(FirewallDriver):

"""Driver which enforces security groups through iptables rules."""

**** CubicPower OpenStack Study ****

    def __init__(self, virtapi, **kwargs):

        super(IptablesFirewallDriver, self).__init__(virtapi)

        self.iptables = linux_net.iptables_manager

        self.instances = {}

        self.network_infos = {}

        self.basically_filtered = False

        # Flags for DHCP request rule

        self.dhcp_create = False

        self.dhcp_created = False

        self.iptables.ipv4['filter'].add_chain('sg-fallback')

        self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')

        self.iptables.ipv6['filter'].add_chain('sg-fallback')

        self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')

**** CubicPower OpenStack Study ****

    def setup_basic_filtering(self, instance, network_info):

        pass

**** CubicPower OpenStack Study ****

    def apply_instance_filter(self, instance, network_info):

        """No-op. Everything is done in prepare_instance_filter."""

        pass

**** CubicPower OpenStack Study ****

    def filter_defer_apply_on(self):

        self.iptables.defer_apply_on()

**** CubicPower OpenStack Study ****

    def filter_defer_apply_off(self):

        self.iptables.defer_apply_off()

**** CubicPower OpenStack Study ****

    def unfilter_instance(self, instance, network_info):

        if self.instances.pop(instance['id'], None):

            # NOTE(vish): use the passed info instead of the stored info

            self.network_infos.pop(instance['id'])

            self.remove_filters_for_instance(instance)

            self.iptables.apply()

        else:

            LOG.info(_('Attempted to unfilter instance which is not '

                     'filtered'), instance=instance)

**** CubicPower OpenStack Study ****

    def prepare_instance_filter(self, instance, network_info):

        self.instances[instance['id']] = instance

        self.network_infos[instance['id']] = network_info

        ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)

        self.add_filters_for_instance(instance, ipv4_rules, ipv6_rules)

        LOG.debug(_('Filters added to instance'), instance=instance)

        self.refresh_provider_fw_rules()

        LOG.debug(_('Provider Firewall Rules refreshed'), instance=instance)

        # Ensure that DHCP request rule is updated if necessary

        if (self.dhcp_create and not self.dhcp_created):

            self.iptables.ipv4['filter'].add_rule(

                    'INPUT',

                    '-s 0.0.0.0/32 -d 255.255.255.255/32 '

                    '-p udp -m udp --sport 68 --dport 67 -j ACCEPT')

            self.iptables.ipv4['filter'].add_rule(

                    'FORWARD',

                    '-s 0.0.0.0/32 -d 255.255.255.255/32 '

                    '-p udp -m udp --sport 68 --dport 67 -j ACCEPT')

            self.dhcp_created = True

        self.iptables.apply()

**** CubicPower OpenStack Study ****

    def _create_filter(self, ips, chain_name):

        return ['-d %s -j $%s' % (ip, chain_name) for ip in ips]

**** CubicPower OpenStack Study ****

    def _get_subnets(self, network_info, version):

        subnets = []

        for vif in network_info:

            if 'network' in vif and 'subnets' in vif['network']:

                for subnet in vif['network']['subnets']:

                    if subnet['version'] == version:

                        subnets.append(subnet)

        return subnets

**** CubicPower OpenStack Study ****

    def _filters_for_instance(self, chain_name, network_info):

        """Creates a rule corresponding to each ip that defines a

           jump to the corresponding instance - chain for all the traffic

           destined to that ip.

        """

        v4_subnets = self._get_subnets(network_info, 4)

        v6_subnets = self._get_subnets(network_info, 6)

        ips_v4 = [ip['address'] for subnet in v4_subnets

                                for ip in subnet['ips']]

        ipv4_rules = self._create_filter(ips_v4, chain_name)

        ipv6_rules = ips_v6 = []

        if CONF.use_ipv6:

            if v6_subnets:

                ips_v6 = [ip['address'] for subnet in v6_subnets

                                        for ip in subnet['ips']]

            ipv6_rules = self._create_filter(ips_v6, chain_name)

        return ipv4_rules, ipv6_rules

**** CubicPower OpenStack Study ****

    def _add_filters(self, chain_name, ipv4_rules, ipv6_rules):

        for rule in ipv4_rules:

            self.iptables.ipv4['filter'].add_rule(chain_name, rule)

        if CONF.use_ipv6:

            for rule in ipv6_rules:

                self.iptables.ipv6['filter'].add_rule(chain_name, rule)

**** CubicPower OpenStack Study ****

    def add_filters_for_instance(self, instance, inst_ipv4_rules,

                                 inst_ipv6_rules):

        network_info = self.network_infos[instance['id']]

        chain_name = self._instance_chain_name(instance)

        if CONF.use_ipv6:

            self.iptables.ipv6['filter'].add_chain(chain_name)

        self.iptables.ipv4['filter'].add_chain(chain_name)

        ipv4_rules, ipv6_rules = self._filters_for_instance(chain_name,

                                                            network_info)

        self._add_filters('local', ipv4_rules, ipv6_rules)

        self._add_filters(chain_name, inst_ipv4_rules, inst_ipv6_rules)

**** CubicPower OpenStack Study ****

    def remove_filters_for_instance(self, instance):

        chain_name = self._instance_chain_name(instance)

        self.iptables.ipv4['filter'].remove_chain(chain_name)

        if CONF.use_ipv6:

            self.iptables.ipv6['filter'].remove_chain(chain_name)

**** CubicPower OpenStack Study ****

    def _instance_chain_name(self, instance):

        return 'inst-%s' % (instance['id'],)

**** CubicPower OpenStack Study ****

    def _do_basic_rules(self, ipv4_rules, ipv6_rules, network_info):

        # Always drop invalid packets

        ipv4_rules += ['-m state --state ' 'INVALID -j DROP']

        ipv6_rules += ['-m state --state ' 'INVALID -j DROP']

        # Allow established connections

        ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']

        ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']

        # Pass through provider-wide drops

        ipv4_rules += ['-j $provider']

        ipv6_rules += ['-j $provider']

**** CubicPower OpenStack Study ****

    def _do_dhcp_rules(self, ipv4_rules, network_info):

        v4_subnets = self._get_subnets(network_info, 4)

        dhcp_servers = [subnet.get_meta('dhcp_server')

            for subnet in v4_subnets if subnet.get_meta('dhcp_server')]

        for dhcp_server in dhcp_servers:

            if dhcp_server:

                ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '

                                  '-j ACCEPT' % (dhcp_server,))

                self.dhcp_create = True

**** CubicPower OpenStack Study ****

    def _do_project_network_rules(self, ipv4_rules, ipv6_rules, network_info):

        v4_subnets = self._get_subnets(network_info, 4)

        v6_subnets = self._get_subnets(network_info, 6)

        cidrs = [subnet['cidr'] for subnet in v4_subnets]

        for cidr in cidrs:

            ipv4_rules.append('-s %s -j ACCEPT' % (cidr,))

        if CONF.use_ipv6:

            cidrv6s = [subnet['cidr'] for subnet in v6_subnets]

            for cidrv6 in cidrv6s:

                ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))

**** CubicPower OpenStack Study ****

    def _do_ra_rules(self, ipv6_rules, network_info):

        v6_subnets = self._get_subnets(network_info, 6)

        gateways_v6 = [subnet['gateway']['address'] for subnet in v6_subnets]

        for gateway_v6 in gateways_v6:

            ipv6_rules.append(

                    '-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,))

**** CubicPower OpenStack Study ****

    def _build_icmp_rule(self, rule, version):

        icmp_type = rule['from_port']

        icmp_code = rule['to_port']

        if icmp_type == -1:

            icmp_type_arg = None

        else:

            icmp_type_arg = '%s' % icmp_type

            if not icmp_code == -1:

                icmp_type_arg += '/%s' % icmp_code

        if icmp_type_arg:

            if version == 4:

                return ['-m', 'icmp', '--icmp-type', icmp_type_arg]

            elif version == 6:

                return ['-m', 'icmp6', '--icmpv6-type', icmp_type_arg]

        # return empty list if icmp_type == -1

        return []

**** CubicPower OpenStack Study ****

    def _build_tcp_udp_rule(self, rule, version):

        if rule['from_port'] == rule['to_port']:

            return ['--dport', '%s' % (rule['from_port'],)]

        else:

            return ['-m', 'multiport',

                    '--dports', '%s:%s' % (rule['from_port'],

                                           rule['to_port'])]

**** CubicPower OpenStack Study ****

    def instance_rules(self, instance, network_info):

        ctxt = context.get_admin_context()

        if isinstance(instance, dict):

            # NOTE(danms): allow old-world instance objects from

            # unconverted callers; all we need is instance.uuid below

            instance = instance_obj.Instance._from_db_object(

                ctxt, instance_obj.Instance(), instance, [])

        ipv4_rules = []

        ipv6_rules = []

        # Initialize with basic rules

        self._do_basic_rules(ipv4_rules, ipv6_rules, network_info)

        # Set up rules to allow traffic to/from DHCP server

        self._do_dhcp_rules(ipv4_rules, network_info)

        #Allow project network traffic

        if CONF.allow_same_net_traffic:

            self._do_project_network_rules(ipv4_rules, ipv6_rules,

                                           network_info)

        # We wrap these in CONF.use_ipv6 because they might cause

        # a DB lookup. The other ones are just list operations, so

        # they're not worth the clutter.

        if CONF.use_ipv6:

            # Allow RA responses

            self._do_ra_rules(ipv6_rules, network_info)

        security_groups = security_group_obj.SecurityGroupList.get_by_instance(

            ctxt, instance)

        # then, security group chains and rules

        for security_group in security_groups:

            rules_cls = security_group_rule_obj.SecurityGroupRuleList

            rules = rules_cls.get_by_security_group(ctxt, security_group)

            for rule in rules:

                LOG.debug(_('Adding security group rule: %r'), rule,

                          instance=instance)

                if not rule['cidr']:

                    version = 4

                else:

                    version = netutils.get_ip_version(rule['cidr'])

                if version == 4:

                    fw_rules = ipv4_rules

                else:

                    fw_rules = ipv6_rules

                protocol = rule['protocol']

                if protocol:

                    protocol = rule['protocol'].lower()

                if version == 6 and protocol == 'icmp':

                    protocol = 'icmpv6'

                args = ['-j ACCEPT']

                if protocol:

                    args += ['-p', protocol]

                if protocol in ['udp', 'tcp']:

                    args += self._build_tcp_udp_rule(rule, version)

                elif protocol == 'icmp':

                    args += self._build_icmp_rule(rule, version)

                if rule['cidr']:

                    LOG.debug('Using cidr %r', rule['cidr'], instance=instance)

                    args += ['-s', str(rule['cidr'])]

                    fw_rules += [' '.join(args)]

                else:

                    if rule['grantee_group']:

                        insts = (

                            instance_obj.InstanceList.get_by_security_group(

                                ctxt, rule['grantee_group']))

                        for instance in insts:

                            if instance['info_cache']['deleted']:

                                LOG.debug('ignoring deleted cache')

                                continue

                            nw_info = compute_utils.get_nw_info_for_instance(

                                    instance)

                            ips = [ip['address']

                                for ip in nw_info.fixed_ips()

                                    if ip['version'] == version]

                            LOG.debug('ips: %r', ips, instance=instance)

                            for ip in ips:

                                subrule = args + ['-s %s' % ip]

                                fw_rules += [' '.join(subrule)]

                LOG.debug('Using fw_rules: %r', fw_rules, instance=instance)

        ipv4_rules += ['-j $sg-fallback']

        ipv6_rules += ['-j $sg-fallback']

        return ipv4_rules, ipv6_rules

**** CubicPower OpenStack Study ****

    def instance_filter_exists(self, instance, network_info):

        pass

**** CubicPower OpenStack Study ****

    def refresh_security_group_members(self, security_group):

        self.do_refresh_security_group_rules(security_group)

        self.iptables.apply()

**** CubicPower OpenStack Study ****

    def refresh_security_group_rules(self, security_group):

        self.do_refresh_security_group_rules(security_group)

        self.iptables.apply()

**** CubicPower OpenStack Study ****

    def refresh_instance_security_rules(self, instance):

        self.do_refresh_instance_rules(instance)

        self.iptables.apply()

    @utils.synchronized('iptables', external=True)

**** CubicPower OpenStack Study ****

    def _inner_do_refresh_rules(self, instance, ipv4_rules,

                                               ipv6_rules):

        self.remove_filters_for_instance(instance)

        self.add_filters_for_instance(instance, ipv4_rules, ipv6_rules)

**** CubicPower OpenStack Study ****

    def do_refresh_security_group_rules(self, security_group):

        for instance in self.instances.values():

            network_info = self.network_infos[instance['id']]

            ipv4_rules, ipv6_rules = self.instance_rules(instance,

                                                         network_info)

            self._inner_do_refresh_rules(instance, ipv4_rules, ipv6_rules)

**** CubicPower OpenStack Study ****

    def do_refresh_instance_rules(self, instance):

        network_info = self.network_infos[instance['id']]

        ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)

        self._inner_do_refresh_rules(instance, ipv4_rules, ipv6_rules)

**** CubicPower OpenStack Study ****

    def refresh_provider_fw_rules(self):

        """See :class:`FirewallDriver` docs."""

        self._do_refresh_provider_fw_rules()

        self.iptables.apply()

    @utils.synchronized('iptables', external=True)

**** CubicPower OpenStack Study ****

    def _do_refresh_provider_fw_rules(self):

        """Internal, synchronized version of refresh_provider_fw_rules."""

        self._purge_provider_fw_rules()

        self._build_provider_fw_rules()

**** CubicPower OpenStack Study ****

    def _purge_provider_fw_rules(self):

        """Remove all rules from the provider chains."""

        self.iptables.ipv4['filter'].empty_chain('provider')

        if CONF.use_ipv6:

            self.iptables.ipv6['filter'].empty_chain('provider')

**** CubicPower OpenStack Study ****

    def _build_provider_fw_rules(self):

        """Create all rules for the provider IP DROPs."""

        self.iptables.ipv4['filter'].add_chain('provider')

        if CONF.use_ipv6:

            self.iptables.ipv6['filter'].add_chain('provider')

        ipv4_rules, ipv6_rules = self._provider_rules()

        for rule in ipv4_rules:

            self.iptables.ipv4['filter'].add_rule('provider', rule)

        if CONF.use_ipv6:

            for rule in ipv6_rules:

                self.iptables.ipv6['filter'].add_rule('provider', rule)

**** CubicPower OpenStack Study ****

    def _provider_rules(self):

        """Generate a list of rules from provider for IP4 & IP6."""

        ctxt = context.get_admin_context()

        ipv4_rules = []

        ipv6_rules = []

        rules = self._virtapi.provider_fw_rule_get_all(ctxt)

        for rule in rules:

            LOG.debug(_('Adding provider rule: %s'), rule['cidr'])

            version = netutils.get_ip_version(rule['cidr'])

            if version == 4:

                fw_rules = ipv4_rules

            else:

                fw_rules = ipv6_rules

            protocol = rule['protocol']

            if version == 6 and protocol == 'icmp':

                protocol = 'icmpv6'

            args = ['-p', protocol, '-s', rule['cidr']]

            if protocol in ['udp', 'tcp']:

                if rule['from_port'] == rule['to_port']:

                    args += ['--dport', '%s' % (rule['from_port'],)]

                else:

                    args += ['-m', 'multiport',

                             '--dports', '%s:%s' % (rule['from_port'],

                                                    rule['to_port'])]

            elif protocol == 'icmp':

                icmp_type = rule['from_port']

                icmp_code = rule['to_port']

                if icmp_type == -1:

                    icmp_type_arg = None

                else:

                    icmp_type_arg = '%s' % icmp_type

                    if not icmp_code == -1:

                        icmp_type_arg += '/%s' % icmp_code

                if icmp_type_arg:

                    if version == 4:

                        args += ['-m', 'icmp', '--icmp-type',

                                 icmp_type_arg]

                    elif version == 6:

                        args += ['-m', 'icmp6', '--icmpv6-type',

                                 icmp_type_arg]

            args += ['-j DROP']

            fw_rules += [' '.join(args)]

        return ipv4_rules, ipv6_rules

**** CubicPower OpenStack Study ****

class NoopFirewallDriver(object):

"""Firewall driver which just provides No-op methods."""

**** CubicPower OpenStack Study ****

    def __init__(self, *args, **kwargs):

        pass

**** CubicPower OpenStack Study ****

    def _noop(self, *args, **kwargs):

        pass

**** CubicPower OpenStack Study ****

    def __getattr__(self, key):

        return self._noop

**** CubicPower OpenStack Study ****

    def instance_filter_exists(self, instance, network_info):

        return True