Allow any port or protocol in security group rules

Neutron allows setting port or protocol wildcard by not specifying any value
for them.

Example, these are allowed by neutron:

    neutron security-group-rule-create --direction egress <sgid>
    neutron security-group-rule-create --direction egress --protocol tcp <sgid>

Specifying '-1' for IP protocol means a wildcard IP protocol.
validate_ip_protocol is updated accordingly.

'All ports' choice is added to 'Open Port' field.

Change-Id: I4a7262eda89e3206c743fee14c78aa6b49308ce6
Closes-Bug: 1669467
This commit is contained in:
Yves-Gwenael Bourhis 2017-03-02 16:13:18 +01:00 committed by Akihiro Motoki
parent 343cbd19b5
commit 87337ff255
5 changed files with 124 additions and 7 deletions

View File

@ -255,8 +255,8 @@ class ValidatorsTests(test.TestCase):
icmp_code) icmp_code)
def test_ip_proto_validator(self): def test_ip_proto_validator(self):
VALID_PROTO = (0, 255) VALID_PROTO = (0, 255, -1)
INVALID_PROTO = (-1, 256) INVALID_PROTO = (-2, 256)
for proto in VALID_PROTO: for proto in VALID_PROTO:
self.assertIsNone(validators.validate_ip_protocol(proto)) self.assertIsNone(validators.validate_ip_protocol(proto))

View File

@ -43,7 +43,7 @@ def validate_icmp_code_range(icmp_code):
def validate_ip_protocol(ip_proto): def validate_ip_protocol(ip_proto):
if ip_proto not in range(0, 256): if ip_proto < -1 or ip_proto > 255:
raise ValidationError(_("Not a valid IP protocol number")) raise ValidationError(_("Not a valid IP protocol number"))

View File

@ -261,9 +261,11 @@ class AddRule(forms.SelfHandlingForm):
backend = api.network.security_group_backend(self.request) backend = api.network.security_group_backend(self.request)
rules_dict = getattr(settings, 'SECURITY_GROUP_RULES', []) rules_dict = getattr(settings, 'SECURITY_GROUP_RULES', [])
common_rules = [(k, rules_dict[k]['name']) common_rules = [
for k in rules_dict (k, rules_dict[k]['name'])
if rules_dict[k].get('backend', backend) == backend] for k in rules_dict
if rules_dict[k].get('backend', backend) == backend
]
common_rules.sort() common_rules.sort()
custom_rules = [('tcp', _('Custom TCP Rule')), custom_rules = [('tcp', _('Custom TCP Rule')),
('udp', _('Custom UDP Rule')), ('udp', _('Custom UDP Rule')),
@ -276,6 +278,10 @@ class AddRule(forms.SelfHandlingForm):
if backend == 'neutron': if backend == 'neutron':
self.fields['direction'].choices = [('ingress', _('Ingress')), self.fields['direction'].choices = [('ingress', _('Ingress')),
('egress', _('Egress'))] ('egress', _('Egress'))]
self.fields['ip_protocol'].help_text = _(
"Enter an integer value between -1 and 255 "
"(-1 means wild card)."
)
else: else:
# direction and ethertype are not supported in Nova secgroup. # direction and ethertype are not supported in Nova secgroup.
self.fields['direction'].widget = forms.HiddenInput() self.fields['direction'].widget = forms.HiddenInput()
@ -284,6 +290,13 @@ class AddRule(forms.SelfHandlingForm):
# and it is available only for neutron security group. # and it is available only for neutron security group.
self.fields['ip_protocol'].widget = forms.HiddenInput() self.fields['ip_protocol'].widget = forms.HiddenInput()
if backend == 'neutron':
self.fields['port_or_range'].choices = [
('port', _('Port')),
('range', _('Port Range')),
('all', _('All ports')),
]
if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
{}).get('enable_ipv6', True): {}).get('enable_ipv6', True):
self.fields['cidr'].version = forms.IPv4 self.fields['cidr'].version = forms.IPv4
@ -323,7 +336,11 @@ class AddRule(forms.SelfHandlingForm):
self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu) self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu)
self._update_and_pop_error(cleaned_data, 'icmp_code', None) self._update_and_pop_error(cleaned_data, 'icmp_code', None)
self._update_and_pop_error(cleaned_data, 'icmp_type', None) self._update_and_pop_error(cleaned_data, 'icmp_type', None)
if port_or_range == "port": if port_or_range == 'all':
self._update_and_pop_error(cleaned_data, 'port', None)
self._update_and_pop_error(cleaned_data, 'from_port', None)
self._update_and_pop_error(cleaned_data, 'to_port', None)
elif port_or_range == "port":
self._update_and_pop_error(cleaned_data, 'from_port', port) self._update_and_pop_error(cleaned_data, 'from_port', port)
self._update_and_pop_error(cleaned_data, 'to_port', port) self._update_and_pop_error(cleaned_data, 'to_port', port)
if port is None: if port is None:

View File

@ -1029,3 +1029,94 @@ class SecurityGroupsNeutronTests(SecurityGroupsViewTests):
res = self.client.post(self.edit_url, formData) res = self.client.post(self.edit_url, formData)
self.assertFormError(res, 'form', 'cidr', self.assertFormError(res, 'form', 'cidr',
'Invalid version for IP address') 'Invalid version for IP address')
@test.create_stubs({api.network: ('security_group_list',
'security_group_backend')})
def test_detail_add_rule_invalid_port(self):
sec_group = self.security_groups.first()
sec_group_list = self.security_groups.list()
rule = self.security_group_rules.first()
api.network.security_group_backend(
IsA(http.HttpRequest)).AndReturn(self.secgroup_backend)
api.network.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
if django.VERSION >= (1, 9):
api.network.security_group_backend(
IsA(http.HttpRequest)).AndReturn(self.secgroup_backend)
api.network.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
self.mox.ReplayAll()
formData = {'method': 'AddRule',
'id': sec_group.id,
'port_or_range': 'port',
'port': -1,
'rule_menu': rule.ip_protocol,
'cidr': rule.ip_range['cidr'],
'remote': 'cidr'}
res = self.client.post(self.edit_url, formData)
self.assertNoMessages()
self.assertContains(res, "Not a valid port number")
@test.create_stubs({api.network: ('security_group_rule_create',
'security_group_list',
'security_group_backend',)})
def test_detail_add_rule_ingress_tcp_without_port(self):
sec_group = self.security_groups.first()
sec_group_list = self.security_groups.list()
rule = self.security_group_rules.list()[3]
api.network.security_group_backend(
IsA(http.HttpRequest)).AndReturn(self.secgroup_backend)
api.network.security_group_rule_create(IsA(http.HttpRequest),
sec_group.id, 'ingress', 'IPv4',
'tcp',
None,
None,
rule.ip_range['cidr'],
None).AndReturn(rule)
api.network.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
self.mox.ReplayAll()
formData = {'id': sec_group.id,
'direction': 'ingress',
'port_or_range': 'all',
'rule_menu': 'tcp',
'cidr': rule.ip_range['cidr'],
'remote': 'cidr'}
res = self.client.post(self.edit_url, formData)
self.assertRedirectsNoFollow(res, self.detail_url)
@test.create_stubs({api.network: ('security_group_rule_create',
'security_group_list',
'security_group_backend',)})
def test_detail_add_rule_custom_without_protocol(self):
sec_group = self.security_groups.first()
sec_group_list = self.security_groups.list()
rule = self.security_group_rules.list()[3]
api.network.security_group_backend(
IsA(http.HttpRequest)).AndReturn(self.secgroup_backend)
api.network.security_group_rule_create(IsA(http.HttpRequest),
sec_group.id, 'ingress', 'IPv4',
None,
None,
None,
rule.ip_range['cidr'],
None).AndReturn(rule)
api.network.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
self.mox.ReplayAll()
formData = {'id': sec_group.id,
'direction': 'ingress',
'port_or_range': 'port',
'rule_menu': 'custom',
'ip_protocol': -1,
'cidr': rule.ip_range['cidr'],
'remote': 'cidr'}
res = self.client.post(self.edit_url, formData)
self.assertRedirectsNoFollow(res, self.detail_url)

View File

@ -0,0 +1,9 @@
---
features:
- |
Securtiy group "Add rule" form now allows to specify 'any' IP protocol
and 'any' port number (for TCP and UDP protocols). This feature is
available when neutron is used as a networking back-end.
You can specify 'any' IP protocol for 'Other Protocol' and ``-1`` means
'any' IP protocol. You can also see ``All ports`` choice in 'Open Port'
field in case of TCP or UDP protocol is selected.