From 134210195578bb568ec48228246fc7e399e80ba2 Mon Sep 17 00:00:00 2001 From: LIU-Yulong Date: Thu, 20 Nov 2014 14:53:48 +0800 Subject: [PATCH] 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 --- doc/source/topics/settings.rst | 13 ++ .../admin/networks/subnets/workflows.py | 24 +++ .../dashboards/project/networks/tests.py | 183 ++++++++++++++++++ .../dashboards/project/networks/workflows.py | 26 +++ .../local/local_settings.py.example | 14 +- openstack_dashboard/test/settings.py | 2 + ...rivate-network-input-5e5bd5978b273c62.yaml | 3 + 7 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bp-restrict-private-network-input-5e5bd5978b273c62.yaml diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst index 460a403204..38f1096bcd 100755 --- a/doc/source/topics/settings.rst +++ b/doc/source/topics/settings.rst @@ -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`` --------------------------- diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py b/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py index dcbb56cfb5..5474d0e0d6 100644 --- a/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py +++ b/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py @@ -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) diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py index dcb832995f..f74dde6e6d 100644 --- a/openstack_dashboard/dashboards/project/networks/tests.py +++ b/openstack_dashboard/dashboards/project/networks/tests.py @@ -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',)}) diff --git a/openstack_dashboard/dashboards/project/networks/workflows.py b/openstack_dashboard/dashboards/project/networks/workflows.py index c9369e90e4..de2eed68d2 100644 --- a/openstack_dashboard/dashboards/project/networks/workflows.py +++ b/openstack_dashboard/dashboards/project/networks/workflows.py @@ -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.') diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 2d77cf74ed..89f5183c57 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -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 \ No newline at end of file +#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': []} diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py index cecabc2571..6e835508c6 100644 --- a/openstack_dashboard/test/settings.py +++ b/openstack_dashboard/test/settings.py @@ -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': []} diff --git a/releasenotes/notes/bp-restrict-private-network-input-5e5bd5978b273c62.yaml b/releasenotes/notes/bp-restrict-private-network-input-5e5bd5978b273c62.yaml new file mode 100644 index 0000000000..fc363e032a --- /dev/null +++ b/releasenotes/notes/bp-restrict-private-network-input-5e5bd5978b273c62.yaml @@ -0,0 +1,3 @@ +--- +features: + - Allows to restrict CIDR range for user private network