Restrict user private network cidr input

If the user's private network has the same CIDR as the public
network, there will be an error.  The router is unable to set
the gateway properly when the private and public CIDR overlap.

This patch add setting 'ALLOWED_PRIVATE_SUBNET_CIDR' to decide
whether to restrict user private network cidr input. And admin
dashboard network panel was not restricted.

Example:
ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': ['192.168.0.0/16',
                                        '10.0.0.0/8'],
                               'ipv6': ['fc00::/7',]}

By default, leave the 'ipv4' and 'ipv6' with empty lists,
then user subnet cidr input will not be restricted.

DocImpact
Implements blueprint: restrict-private-network-input
Change-Id: I6b2ee58447d517c1c40344b8f4dd95968638da5b
This commit is contained in:
LIU-Yulong 2014-11-20 14:53:48 +08:00 committed by LIU Yulong
parent a4920d8176
commit 1342101955
7 changed files with 264 additions and 1 deletions

View File

@ -1610,6 +1610,19 @@ Ignore all listed Nova extensions, and behave as if they were unsupported.
Can be used to selectively disable certain costly extensions for performance
reasons.
``ALLOWED_PRIVATE_SUBNET_CIDR``
-------------------------------
.. versionadded:: 10.0.0(Newton)
Default: ``{'ipv4': [], 'ipv6': []}``
Dict used to restrict user private subnet cidr range.
An empty list means that user input will not be restricted
for a corresponding IP version. By default, there is
no restriction for both IPv4 and IPv6.
Example: ``{'ipv4': ['192.168.0.0/16', '10.0.0.0/8'], 'ipv6': ['fc00::/7',]}``
``ADMIN_FILTER_DATA_FIRST``
---------------------------

View File

@ -22,12 +22,25 @@ from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.subnets \
import workflows as project_workflows
from openstack_dashboard.dashboards.project.networks import workflows \
as net_workflows
LOG = logging.getLogger(__name__)
class CreateSubnetInfoAction(project_workflows.CreateSubnetInfoAction):
check_subnet_range = False
class CreateSubnetInfo(project_workflows.CreateSubnetInfo):
action_class = CreateSubnetInfoAction
class CreateSubnet(project_workflows.CreateSubnet):
default_steps = (CreateSubnetInfo,
net_workflows.CreateSubnetDetail)
def get_success_url(self):
return reverse("horizon:admin:networks:detail",
args=(self.context.get('network_id'),))
@ -53,6 +66,17 @@ class CreateSubnet(project_workflows.CreateSubnet):
return True if subnet else False
class UpdateSubnetInfoAction(project_workflows.UpdateSubnetInfoAction):
check_subnet_range = False
class UpdateSubnetInfo(project_workflows.UpdateSubnetInfo):
action_class = UpdateSubnetInfoAction
class UpdateSubnet(project_workflows.UpdateSubnet):
success_url = "horizon:admin:networks:detail"
failure_url = "horizon:admin:networks:detail"
default_steps = (UpdateSubnetInfo,
project_workflows.UpdateSubnetDetail)

View File

@ -765,6 +765,189 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
self.test_network_create_post_with_subnet_cidr_without_mask(
test_with_subnetpool=True)
@test.update_settings(
ALLOWED_PRIVATE_SUBNET_CIDR={'ipv4': ['192.168.0.0/16']})
@test.create_stubs({api.neutron: ('is_extension_supported',
'profile_list',
'subnetpool_list')})
def test_network_create_post_with_subnet_cidr_invalid_v4_range(
self,
test_with_profile=False,
test_with_subnetpool=False
):
network = self.networks.first()
subnet = self.subnets.first()
if test_with_profile:
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'shared': False,
'admin_state': network.admin_state_up,
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
form_data['prefixlen'] = subnetpool.default_prefixlen
form_data.update(form_data_subnet(subnet, cidr='30.30.30.0/24',
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
expected_msg = ("CIDRs allowed for user private ipv4 networks "
"are 192.168.0.0/16.")
self.assertContains(res, expected_msg)
@test.update_settings(
ALLOWED_PRIVATE_SUBNET_CIDR={'ipv4': ['192.168.0.0/16']})
@test.update_settings(
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
def test_network_create_post_with_subnet_cidr_invalid_v4_range_w_profile(
self):
self.test_network_create_post_with_subnet_cidr_invalid_v4_range(
test_with_profile=True)
@test.update_settings(
ALLOWED_PRIVATE_SUBNET_CIDR={'ipv4': ['192.168.0.0/16']})
def test_network_create_post_with_subnet_cidr_invalid_v4_range_w_snpool(
self):
self.test_network_create_post_with_subnet_cidr_invalid_v4_range(
test_with_subnetpool=True)
@test.update_settings(ALLOWED_PRIVATE_SUBNET_CIDR={'ipv6': ['fc00::/9']})
@test.create_stubs({api.neutron: ('is_extension_supported',
'profile_list',
'subnetpool_list')})
def test_network_create_post_with_subnet_cidr_invalid_v6_range(
self,
test_with_profile=False,
test_with_subnetpool=False
):
network = self.networks.first()
subnet_v6 = self.subnets.list()[3]
if test_with_profile:
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'shared': False,
'admin_state': network.admin_state_up,
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
form_data['prefixlen'] = subnetpool.default_prefixlen
form_data.update(form_data_subnet(subnet_v6, cidr='fc00::/7',
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
expected_msg = ("CIDRs allowed for user private ipv6 networks "
"are fc00::/9.")
self.assertContains(res, expected_msg)
@test.update_settings(ALLOWED_PRIVATE_SUBNET_CIDR={'ipv6': ['fc00::/9']})
@test.update_settings(
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
def test_network_create_post_with_subnet_cidr_invalid_v6_range_w_profile(
self):
self.test_network_create_post_with_subnet_cidr_invalid_v6_range(
test_with_profile=True)
@test.update_settings(ALLOWED_PRIVATE_SUBNET_CIDR={'ipv6': ['fc00::/9']})
def test_network_create_post_with_subnet_cidr_invalid_v6_range_w_snpool(
self):
self.test_network_create_post_with_subnet_cidr_invalid_v4_range(
test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('network_create',
'subnet_create',
'profile_list',
'is_extension_supported',
'subnetpool_list')})
def test_network_create_post_with_subnet_cidr_not_restrict(
self,
test_with_profile=False
):
network = self.networks.first()
subnet = self.subnets.first()
cidr = '30.30.30.0/24'
gateway_ip = '30.30.30.1'
params = {'name': network.name,
'admin_state_up': network.admin_state_up,
'shared': False}
subnet_params = {'network_id': network.id,
'name': subnet.name,
'cidr': cidr,
'ip_version': subnet.ip_version,
'gateway_ip': gateway_ip,
'enable_dhcp': subnet.enable_dhcp}
if test_with_profile:
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndReturn(network)
api.neutron.subnet_create(IsA(http.HttpRequest),
**subnet_params).AndReturn(subnet)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'shared': False,
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, cidr=cidr,
gateway_ip=gateway_ip,
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.update_settings(
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
def test_network_create_post_with_subnet_cidr_not_restrict_w_profile(self):
self.test_network_create_post_with_subnet_cidr_not_restrict(
test_with_profile=True)
@test.create_stubs({api.neutron: ('profile_list',
'is_extension_supported',
'subnetpool_list',)})

View File

@ -199,6 +199,8 @@ class CreateSubnetInfoAction(workflows.Action):
initial=False,
required=False)
check_subnet_range = True
class Meta(object):
name = _("Subnet")
help_text = _('Creates a subnet associated with the network.'
@ -268,6 +270,28 @@ class CreateSubnetInfoAction(workflows.Action):
self.fields['subnetpool'].widget = forms.HiddenInput()
self.fields['prefixlen'].widget = forms.HiddenInput()
def _check_subnet_range(self, subnet, allow_cidr):
allowed_net = netaddr.IPNetwork(allow_cidr)
return subnet in allowed_net
def _check_cidr_allowed(self, ip_version, subnet):
if not self.check_subnet_range:
return
allowed_cidr = getattr(settings, "ALLOWED_PRIVATE_SUBNET_CIDR", {})
version_str = 'ipv%s' % ip_version
allowed_ranges = allowed_cidr.get(version_str, [])
if allowed_ranges:
under_range = any(self._check_subnet_range(subnet, allowed_range)
for allowed_range in allowed_ranges)
if not under_range:
range_str = ', '.join(allowed_ranges)
msg = (_("CIDRs allowed for user private %(ip_ver)s "
"networks are %(allowed)s.") %
{'ip_ver': '%s' % version_str,
'allowed': range_str})
raise forms.ValidationError(msg)
def _check_subnet_data(self, cleaned_data, is_create=True):
cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version'))
@ -295,6 +319,8 @@ class CreateSubnetInfoAction(workflows.Action):
msg = _("The subnet in the Network Address is "
"too small (/%s).") % subnet.prefixlen
self._errors['cidr'] = self.error_class([msg])
self._check_cidr_allowed(ip_version, subnet)
if not no_gateway and gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.')

View File

@ -783,4 +783,16 @@ REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES',
# To allow operators to require admin users provide a search criteria first
# before loading any data into the admin views, set the following attribute to
# True
#ADMIN_FILTER_DATA_FIRST=False
#ADMIN_FILTER_DATA_FIRST=False
# Dict used to restrict user private subnet cidr range.
# An empty list means that user input will not be restricted
# for a corresponding IP version. By default, there is
# no restriction for IPv4 or IPv6. To restrict
# user private subnet cidr range set ALLOWED_PRIVATE_SUBNET_CIDR
# to something like
#ALLOWED_PRIVATE_SUBNET_CIDR = {
# 'ipv4': ['10.0.0.0/8', '192.168.0.0/16'],
# 'ipv6': ['fc00::/7']
#}
ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': [], 'ipv6': []}

View File

@ -259,3 +259,5 @@ REST_API_SETTING_2 = 'bar'
REST_API_SECURITY = 'SECURITY'
REST_API_REQUIRED_SETTINGS = ['REST_API_SETTING_1']
REST_API_ADDITIONAL_SETTINGS = ['REST_API_SETTING_2']
ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': [], 'ipv6': []}

View File

@ -0,0 +1,3 @@
---
features:
- Allows to restrict CIDR range for user private network <https://blueprints.launchpad.net/horizon/+spec/restrict-private-network-input>