Merge "Restrict user private network cidr input"
This commit is contained in:
commit
0e0b01fd27
@ -1615,6 +1615,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.')
|
||||
|
@ -784,3 +784,15 @@ REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES',
|
||||
# before loading any data into the admin views, set the following attribute to
|
||||
# True
|
||||
#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': []}
|
||||
|
@ -265,3 +265,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