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:
parent
a4920d8176
commit
1342101955
@ -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``
|
||||
---------------------------
|
||||
|
@ -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)
|
||||
|
@ -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',)})
|
||||
|
@ -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.')
|
||||
|
@ -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': []}
|
||||
|
@ -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': []}
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Allows to restrict CIDR range for user private network <https://blueprints.launchpad.net/horizon/+spec/restrict-private-network-input>
|
Loading…
Reference in New Issue
Block a user