Skip to content

Commit 60f579b

Browse files
committed
allowed_cidr validation for additional_vips
The validation for the allowed_cidr parameter did not take into account the IP version of additional vIPs. The parameter was rejected if the IP version did not match the IP version of the primary vIP. As additional vIPs can have a different IP version from the primary vIP all vIPs must be checked during validation. Change-Id: I3706fd5e12e17be37edce974563c6806d4f09709
1 parent 3475a4b commit 60f579b

File tree

5 files changed

+107
-37
lines changed

5 files changed

+107
-37
lines changed

octavia/api/v2/controllers/listener.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,18 @@ def _validate_insert_headers(self, insert_header_list, listener_protocol):
156156
value=headers,
157157
option='%s protocol listener.' % listener_protocol)
158158

159-
def _validate_cidr_compatible_with_vip(self, vip, allowed_cidrs):
159+
def _validate_cidr_compatible_with_vip(self, vips, allowed_cidrs):
160160
for cidr in allowed_cidrs:
161-
# Check if CIDR IP version matches VIP IP version
162-
if common_utils.is_cidr_ipv6(cidr) != common_utils.is_ipv6(vip):
163-
msg = _("CIDR %(cidr)s IP version incompatible with VIP "
164-
"%(vip)s IP version.")
161+
for vip in vips:
162+
# Check if CIDR IP version matches VIP IP version
163+
if (common_utils.is_cidr_ipv6(cidr) ==
164+
common_utils.is_ipv6(vip)):
165+
break
166+
else:
167+
msg = _("CIDR %(cidr)s IP version incompatible with all VIPs "
168+
"%(vips)s IP version.")
165169
raise exceptions.ValidationException(
166-
detail=msg % {'cidr': cidr, 'vip': vip})
170+
detail=msg % {'cidr': cidr, 'vips': vips})
167171

168172
def _validate_create_listener(self, lock_session, listener_dict):
169173
"""Validate listener for wrong protocol or duplicate listeners
@@ -306,10 +310,11 @@ def _validate_create_listener(self, lock_session, listener_dict):
306310
# Validate allowed CIDRs
307311
allowed_cidrs = listener_dict.get('allowed_cidrs', []) or []
308312
lb_id = listener_dict.get('load_balancer_id')
309-
vip_db = self.repositories.vip.get(
310-
lock_session, load_balancer_id=lb_id)
311-
vip_address = vip_db.ip_address
312-
self._validate_cidr_compatible_with_vip(vip_address, allowed_cidrs)
313+
lb_db = self.repositories.load_balancer.get(
314+
lock_session, id=lb_id)
315+
vip_addresses = [lb_db.vip.ip_address]
316+
vip_addresses.extend([vip.ip_address for vip in lb_db.additional_vips])
317+
self._validate_cidr_compatible_with_vip(vip_addresses, allowed_cidrs)
313318

314319
if _can_tls_offload:
315320
# Validate TLS version list
@@ -525,9 +530,13 @@ def _validate_listener_PUT(self, listener, db_listener):
525530

526531
# Validate allowed CIDRs
527532
if (listener.allowed_cidrs and listener.allowed_cidrs != wtypes.Unset):
528-
vip_address = db_listener.load_balancer.vip.ip_address
533+
vip_addresses = [db_listener.load_balancer.vip.ip_address]
534+
vip_addresses.extend(
535+
[vip.ip_address
536+
for vip in db_listener.load_balancer.additional_vips]
537+
)
529538
self._validate_cidr_compatible_with_vip(
530-
vip_address, listener.allowed_cidrs)
539+
vip_addresses, listener.allowed_cidrs)
531540

532541
# Check TLS cipher prohibit list
533542
if listener.tls_ciphers:

octavia/network/drivers/neutron/allowed_address_pairs.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ def _get_ethertype_for_ip(self, ip):
147147
address = ipaddress.ip_address(ip)
148148
return 'IPv6' if address.version == 6 else 'IPv4'
149149

150+
def _get_ethertype_for_cidr(self, cidr):
151+
net = ipaddress.ip_network(cidr)
152+
return 'IPv6' if net.version == 6 else 'IPv4'
153+
150154
def _update_security_group_rules(self, load_balancer, sec_grp_id):
151155
rules = self.neutron_client.list_security_group_rules(
152156
security_group_id=sec_grp_id)
@@ -224,11 +228,15 @@ def _update_security_group_rules(self, load_balancer, sec_grp_id):
224228
ethertypes.add(self._get_ethertype_for_ip(add_vip.ip_address))
225229
for port_protocol in add_ports:
226230
for ethertype in ethertypes:
227-
self._create_security_group_rule(sec_grp_id, port_protocol[1],
228-
port_min=port_protocol[0],
229-
port_max=port_protocol[0],
230-
ethertype=ethertype,
231-
cidr=port_protocol[2])
231+
cidr = port_protocol[2]
232+
if not cidr or self._get_ethertype_for_cidr(cidr) == ethertype:
233+
self._create_security_group_rule(
234+
sec_grp_id, port_protocol[1],
235+
port_min=port_protocol[0],
236+
port_max=port_protocol[0],
237+
ethertype=ethertype,
238+
cidr=cidr,
239+
)
232240

233241
# Currently we are using the VIP network for VRRP
234242
# so we need to open up the protocols for it

octavia/tests/functional/api/v2/test_listener.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,9 +1207,9 @@ def test_create_with_bad_ca_cert(self):
12071207
"It must be a valid x509 PEM format certificate.",
12081208
response['faultstring'])
12091209

1210-
def _test_create_with_allowed_cidrs(self, allowed_cidrs):
1210+
def _test_create_with_allowed_cidrs(self, allowed_cidrs, lb_id):
12111211
listener = self.create_listener(constants.PROTOCOL_TCP,
1212-
80, self.lb_id,
1212+
80, lb_id,
12131213
allowed_cidrs=allowed_cidrs)
12141214
listener_path = self.LISTENER_PATH.format(
12151215
listener_id=listener['listener']['id'])
@@ -1218,14 +1218,17 @@ def _test_create_with_allowed_cidrs(self, allowed_cidrs):
12181218

12191219
def test_create_with_allowed_cidrs_ipv4(self):
12201220
allowed_cidrs = ['10.0.1.0/24', '172.16.55.0/25']
1221-
self._test_create_with_allowed_cidrs(allowed_cidrs)
1221+
self._test_create_with_allowed_cidrs(allowed_cidrs, self.lb_id)
12221222

12231223
def test_create_with_allowed_cidrs_ipv6(self):
1224+
lb_ipv6 = self.create_load_balancer(
1225+
uuidutils.generate_uuid(),
1226+
vip_address='2001:db9:a1b:13f0::1',
1227+
)
1228+
lb_id = lb_ipv6.get('loadbalancer').get('id')
1229+
self.set_lb_status(lb_id)
12241230
allowed_cidrs = ['2001:db8:a0b:12f0::/64', '2a02:8071:69e::/64']
1225-
with mock.patch('octavia.db.repositories.VipRepository.'
1226-
'get') as repo_mock:
1227-
repo_mock.return_value.ip_address = "2001:db9:a1b:13f0::1"
1228-
self._test_create_with_allowed_cidrs(allowed_cidrs)
1231+
self._test_create_with_allowed_cidrs(allowed_cidrs, lb_id)
12291232

12301233
def test_create_with_bad_allowed_cidrs(self):
12311234
allowed_cidrs = [u'10.0.1.0/33', u'172.16.55.1.0/25']
@@ -1251,24 +1254,42 @@ def test_create_with_incompatible_allowed_cidrs_ipv6(self):
12511254
body = self._build_body(lb_listener)
12521255
response = self.post(self.LISTENERS_PATH, body, status=400).json
12531256
self.assertIn("Validation failure: CIDR 2001:db8:a0b:12f0::/64 IP "
1254-
"version incompatible with VIP 198.0.2.5 IP version.",
1257+
"version incompatible with all VIPs ['198.0.2.5'] IP "
1258+
"version.",
12551259
response['faultstring'])
12561260

12571261
def test_create_with_incompatible_allowed_cidrs_ipv4(self):
1262+
lb_ipv6 = self.create_load_balancer(
1263+
uuidutils.generate_uuid(),
1264+
vip_address='2001:db9:a1b:13f0::1',
1265+
)
1266+
lb_id = lb_ipv6.get('loadbalancer').get('id')
1267+
self.set_lb_status(lb_id)
12581268
lb_listener = {
12591269
'protocol': constants.PROTOCOL_TCP,
12601270
'protocol_port': 80,
12611271
'project_id': self.project_id,
1262-
'loadbalancer_id': self.lb_id,
1272+
'loadbalancer_id': lb_id,
12631273
'allowed_cidrs': ['10.0.1.0/24']}
1264-
with mock.patch('octavia.db.repositories.VipRepository.'
1265-
'get') as repo_mock:
1266-
repo_mock.return_value.ip_address = "2001:db9:a1b:13f0::1"
1267-
body = self._build_body(lb_listener)
1268-
response = self.post(self.LISTENERS_PATH, body, status=400).json
1269-
self.assertIn("Validation failure: CIDR 10.0.1.0/24 IP version "
1270-
"incompatible with VIP 2001:db9:a1b:13f0::1 IP "
1271-
"version.", response['faultstring'])
1274+
body = self._build_body(lb_listener)
1275+
response = self.post(self.LISTENERS_PATH, body, status=400).json
1276+
self.assertIn("Validation failure: CIDR 10.0.1.0/24 IP version "
1277+
"incompatible with all VIPs "
1278+
"['2001:db9:a1b:13f0::1'] IP version.",
1279+
response['faultstring'])
1280+
1281+
def test_create_with_mixed_version_allowed_cidrs(self):
1282+
lb_dualstack = self.create_load_balancer(
1283+
uuidutils.generate_uuid(),
1284+
additional_vips=[{'subnet_id': uuidutils.generate_uuid(),
1285+
'ip_address': '2001:db9:a1b:13f0::1',
1286+
}],
1287+
)
1288+
lb_id = lb_dualstack.get('loadbalancer').get('id')
1289+
self.set_lb_status(lb_id)
1290+
self._test_create_with_allowed_cidrs(['10.0.1.0/24',
1291+
'2001:db9:a1b:13f0::/64'],
1292+
lb_id)
12721293

12731294
def test_create_with_duplicated_allowed_cidrs(self):
12741295
allowed_cidrs = ['10.0.1.0/24', '10.0.2.0/24', '10.0.2.0/24']

octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,12 +1099,13 @@ def test_update_vip(self):
10991099
lc_1 = data_models.ListenerCidr('l1', '10.0.101.0/24')
11001100
lc_2 = data_models.ListenerCidr('l2', '10.0.102.0/24')
11011101
lc_3 = data_models.ListenerCidr('l2', '10.0.103.0/24')
1102+
lc_4 = data_models.ListenerCidr('l2', '2001:0DB8::/32')
11021103
listeners = [data_models.Listener(protocol_port=80, peer_port=1024,
11031104
protocol=constants.PROTOCOL_TCP,
11041105
allowed_cidrs=[lc_1]),
11051106
data_models.Listener(protocol_port=443, peer_port=1025,
11061107
protocol=constants.PROTOCOL_TCP,
1107-
allowed_cidrs=[lc_2, lc_3]),
1108+
allowed_cidrs=[lc_2, lc_3, lc_4]),
11081109
data_models.Listener(protocol_port=50, peer_port=1026,
11091110
protocol=constants.PROTOCOL_UDP)]
11101111
vip = data_models.Vip(ip_address='10.0.0.2')
@@ -1183,7 +1184,18 @@ def test_update_vip(self):
11831184
'remote_ip_prefix': '10.0.103.0/24'
11841185
}
11851186
}
1186-
expected_create_rule_udp = {
1187+
expected_create_rule_5 = {
1188+
'security_group_rule': {
1189+
'security_group_id': 'secgrp-1',
1190+
'direction': 'ingress',
1191+
'protocol': 'tcp',
1192+
'port_range_min': 443,
1193+
'port_range_max': 443,
1194+
'ethertype': 'IPv6',
1195+
'remote_ip_prefix': '2001:0DB8::/32'
1196+
}
1197+
}
1198+
expected_create_rule_udp_1 = {
11871199
'security_group_rule': {
11881200
'security_group_id': 'secgrp-1',
11891201
'direction': 'ingress',
@@ -1194,13 +1206,26 @@ def test_update_vip(self):
11941206
'remote_ip_prefix': None
11951207
}
11961208
}
1209+
expected_create_rule_udp_2 = {
1210+
'security_group_rule': {
1211+
'security_group_id': 'secgrp-1',
1212+
'direction': 'ingress',
1213+
'protocol': 'udp',
1214+
'port_range_min': 50,
1215+
'port_range_max': 50,
1216+
'ethertype': 'IPv6',
1217+
'remote_ip_prefix': None
1218+
}
1219+
}
11971220

11981221
create_rule.assert_has_calls([mock.call(expected_create_rule_1),
11991222
mock.call(expected_create_rule_udp_peer),
12001223
mock.call(expected_create_rule_2),
12011224
mock.call(expected_create_rule_3),
12021225
mock.call(expected_create_rule_4),
1203-
mock.call(expected_create_rule_udp)],
1226+
mock.call(expected_create_rule_5),
1227+
mock.call(expected_create_rule_udp_1),
1228+
mock.call(expected_create_rule_udp_2)],
12041229
any_order=True)
12051230

12061231
def test_update_vip_when_protocol_and_peer_ports_overlap(self):
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
The validation for the allowed_cidr parameter only took into account the
5+
IP version of the primary VIP. CIDRs which only matched the version of an
6+
additonal VIP were rejected. This if fixed and CIDRs are now matched
7+
against the IP version of all VIPs.

0 commit comments

Comments
 (0)