Skip to content

Commit 51c8022

Browse files
committed
Issue 4404: Add support for "icmpv6" protocol to ASGs
* "icmpv6" can be used if "enable_ipv6" is configured * "icmp" destinations may only consist of IPv4 addresses * "icmpv6" destinations may only consist of IPv6 addresses
1 parent 24bad0c commit 51c8022

File tree

3 files changed

+230
-11
lines changed

3 files changed

+230
-11
lines changed

app/messages/validators/security_group_rule_validator.rb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ def validate(record)
2727

2828
validate_allowed_keys(rule, record, index)
2929

30-
add_rule_error("protocol must be 'tcp', 'udp', 'icmp', or 'all'", record, index) unless valid_protocol(rule[:protocol])
30+
add_rule_error("protocol must be 'tcp', 'udp', 'icmp', 'icmpv6' or 'all'", record, index) unless valid_protocol(rule[:protocol])
31+
32+
if rule[:protocol] == 'icmp'
33+
allowed_ip_version = NetAddr::IPv4Net
34+
elsif rule[:protocol] == 'icmpv6'
35+
allowed_ip_version = NetAddr::IPv6Net
36+
else
37+
allowed_ip_version = nil
38+
end
3139

3240
if valid_destination_type(rule[:destination], record, index)
3341
destinations = rule[:destination].split(',', -1)
3442
add_rule_error("maximum destinations per rule exceeded - must be under #{MAX_DESTINATIONS_PER_RULE}", record, index) unless destinations.length <= MAX_DESTINATIONS_PER_RULE
3543

3644
destinations.each do |d|
37-
validate_destination(d, record, index)
45+
validate_destination(d, rule[:protocol], allowed_ip_version, record, index)
3846
end
3947
end
4048

@@ -46,6 +54,9 @@ def validate(record)
4654
validate_tcp_udp_protocol(rule, record, index)
4755
when 'icmp'
4856
validate_icmp_protocol(rule, record, index)
57+
when 'icmpv6'
58+
add_rule_error("icmpv6 cannot be used if enable_ipv6 is false", record, index) unless CloudController::RuleValidator.ipv6_enabled?
59+
validate_icmp_protocol(rule, record, index)
4960
when 'all'
5061
add_rule_error('ports are not allowed for protocols of type all', record, index) if rule[:ports]
5162
end
@@ -57,7 +68,7 @@ def boolean?(value)
5768
end
5869

5970
def valid_protocol(protocol)
60-
protocol.is_a?(String) && %w[tcp udp icmp all].include?(protocol)
71+
protocol.is_a?(String) && %w[tcp udp icmp icmpv6 all].include?(protocol)
6172
end
6273

6374
def validate_allowed_keys(rule, record, index)
@@ -128,7 +139,7 @@ def valid_destination_type(destination, record, index)
128139
true
129140
end
130141

131-
def validate_destination(destination, record, index)
142+
def validate_destination(destination, protocol, allowed_ip_version, record, index)
132143
error_message = 'destination must be a valid CIDR, IP address, or IP address range'
133144
error_message = 'destination must contain valid CIDR(s), IP address(es), or IP address range(s)' if CloudController::RuleValidator.comma_delimited_destinations_enabled?
134145
add_rule_error('empty destination specified in comma-delimited list', record, index) if destination.empty?
@@ -139,8 +150,9 @@ def validate_destination(destination, record, index)
139150
add_rule_error(zeros_error_message, record, index) unless CloudController::RuleValidator.no_leading_zeros(address_list)
140151

141152
if address_list.length == 1
142-
add_rule_error(error_message, record, index) unless CloudController::RuleValidator.parse_ip(address_list.first)
143-
153+
parsed_ip = CloudController::RuleValidator.parse_ip(address_list.first)
154+
add_rule_error(error_message, record, index) unless parsed_ip
155+
add_rule_error("for protocol \"#{protocol}\" you cannot use IPv#{parsed_ip.version} addresses", record, index) unless parsed_ip.nil? || allowed_ip_version.nil? || parsed_ip.is_a?(allowed_ip_version)
144156
elsif address_list.length == 2
145157
ips = CloudController::RuleValidator.parse_ip(address_list)
146158
return add_rule_error('destination IP address range is invalid', record, index) unless ips
@@ -153,6 +165,7 @@ def validate_destination(destination, record, index)
153165

154166
reversed_range_error = 'beginning of IP address range is numerically greater than the end of its range (range endpoints are inverted)'
155167
add_rule_error(reversed_range_error, record, index) unless ips.first == sorted_ips.first
168+
add_rule_error("for protocol \"#{protocol}\" you cannot use IPv#{ips.first.version} addresses", record, index) unless ips.first.nil? || allowed_ip_version.nil? || ips.first.is_a?(allowed_ip_version)
156169

157170
else
158171
add_rule_error(error_message, record, index)

lib/cloud_controller/rule_validator.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def self.parse_ip(val)
7777
ipv4 || ipv6
7878
end
7979

80+
def self.ipv6_enabled?
81+
config.get(:enable_ipv6)
82+
end
83+
8084
def self.comma_delimited_destinations_enabled?
8185
config.get(:security_groups, :enable_comma_delimited_destinations)
8286
end

spec/unit/messages/validators/security_group_rule_validator_spec.rb

Lines changed: 207 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ def self.name
4848

4949
it 'returns indexed errors corresponding to each invalid rule' do
5050
expect(subject).not_to be_valid
51-
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', or 'all'"
51+
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', 'icmpv6' or 'all'"
5252
expect(subject.errors.full_messages).to include 'Rules[0]: destination must be a valid CIDR, IP address, or IP address range'
53-
expect(subject.errors.full_messages).to include "Rules[1]: protocol must be 'tcp', 'udp', 'icmp', or 'all'"
53+
expect(subject.errors.full_messages).to include "Rules[1]: protocol must be 'tcp', 'udp', 'icmp', 'icmpv6' or 'all'"
5454
expect(subject.errors.full_messages).to include 'Rules[1]: destination must be a valid CIDR, IP address, or IP address range'
5555
end
5656
end
@@ -1064,7 +1064,7 @@ def self.name
10641064

10651065
it 'adds an error' do
10661066
expect(subject).not_to be_valid
1067-
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', or 'all'"
1067+
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', 'icmpv6' or 'all'"
10681068
end
10691069
end
10701070

@@ -1073,7 +1073,7 @@ def self.name
10731073

10741074
it 'is not valid' do
10751075
expect(subject).not_to be_valid
1076-
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', or 'all'"
1076+
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', 'icmpv6' or 'all'"
10771077
end
10781078
end
10791079

@@ -1082,7 +1082,7 @@ def self.name
10821082

10831083
it 'adds an error' do
10841084
expect(subject).not_to be_valid
1085-
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', or 'all'"
1085+
expect(subject.errors.full_messages).to include "Rules[0]: protocol must be 'tcp', 'udp', 'icmp', 'icmpv6' or 'all'"
10861086
end
10871087
end
10881088

@@ -1303,6 +1303,208 @@ def self.name
13031303
expect(subject.errors.full_messages).to include 'Rules[0]: code must be an integer between -1 and 255 (inclusive)'
13041304
end
13051305
end
1306+
1307+
context 'ipv6 is disabled' do
1308+
before do
1309+
TestConfig.config[:enable_ipv6] = false
1310+
end
1311+
1312+
context 'icmpv6 protocol in a rule' do
1313+
let(:rules) do
1314+
[
1315+
{
1316+
protocol: 'icmpv6',
1317+
destination: '2001:db8::/32',
1318+
type: -1,
1319+
code: 255
1320+
}
1321+
]
1322+
end
1323+
1324+
it 'is not valid' do
1325+
expect(subject).not_to be_valid
1326+
expect(subject.errors.full_messages).to include 'Rules[0]: icmpv6 cannot be used if enable_ipv6 is false'
1327+
end
1328+
end
1329+
end
1330+
1331+
context 'ipv6 is enabled' do
1332+
before do
1333+
TestConfig.config[:enable_ipv6] = true
1334+
end
1335+
1336+
context 'icmp protocol contains an IPv6 destination' do
1337+
let(:rules) do
1338+
[
1339+
{
1340+
protocol: 'icmp',
1341+
destination: '2001:db8::/32',
1342+
type: -1,
1343+
code: 255
1344+
}
1345+
]
1346+
end
1347+
1348+
it 'is invalid' do
1349+
expect(subject).not_to be_valid
1350+
expect(subject.errors.full_messages).to include 'Rules[0]: for protocol "icmp" you cannot use IPv6 addresses'
1351+
end
1352+
end
1353+
1354+
context 'icmp protocol contains an IPv6 destination range' do
1355+
let(:rules) do
1356+
[
1357+
{
1358+
protocol: 'icmp',
1359+
destination: '2001:0db8::1-2001:0db8::ff',
1360+
type: -1,
1361+
code: 255
1362+
}
1363+
]
1364+
end
1365+
1366+
it 'is invalid' do
1367+
expect(subject).not_to be_valid
1368+
expect(subject.errors.full_messages).to include 'Rules[0]: for protocol "icmp" you cannot use IPv6 addresses'
1369+
end
1370+
end
1371+
1372+
context 'icmp protocol contains a comma-delimited list of IPv6 destinations' do
1373+
before do
1374+
TestConfig.config[:security_groups][:enable_comma_delimited_destinations] = true
1375+
end
1376+
1377+
let(:rules) do
1378+
[
1379+
{
1380+
protocol: 'icmp',
1381+
destination: '2001:db8::/32,2001:db8:85a3::/64',
1382+
type: -1,
1383+
code: 255
1384+
}
1385+
]
1386+
end
1387+
1388+
it 'is invalid' do
1389+
expect(subject).not_to be_valid
1390+
expect(subject.errors.full_messages).to include 'Rules[0]: for protocol "icmp" you cannot use IPv6 addresses'
1391+
end
1392+
end
1393+
1394+
context 'icmpv6 protocol contains an IPv6 destination' do
1395+
let(:rules) do
1396+
[
1397+
{
1398+
protocol: 'icmpv6',
1399+
destination: '2001:db8::/32',
1400+
type: -1,
1401+
code: 255
1402+
}
1403+
]
1404+
end
1405+
1406+
it 'is valid' do
1407+
expect(subject).to be_valid
1408+
end
1409+
end
1410+
1411+
context 'icmpv6 protocol contains a comma-delimited list of IPv6 destinations' do
1412+
before do
1413+
TestConfig.config[:security_groups][:enable_comma_delimited_destinations] = true
1414+
end
1415+
1416+
let(:rules) do
1417+
[
1418+
{
1419+
protocol: 'icmpv6',
1420+
destination: '2001:db8::/32,2001:db8:85a3::/64',
1421+
type: -1,
1422+
code: 255
1423+
}
1424+
]
1425+
end
1426+
1427+
it 'is valid' do
1428+
expect(subject).to be_valid
1429+
end
1430+
end
1431+
1432+
context 'icmpv6 protocol contains an IPv4 destination' do
1433+
let(:rules) do
1434+
[
1435+
{
1436+
protocol: 'icmpv6',
1437+
destination: '10.0.0.0/8',
1438+
type: -1,
1439+
code: 255
1440+
}
1441+
]
1442+
end
1443+
1444+
it 'is invalid' do
1445+
expect(subject).not_to be_valid
1446+
expect(subject.errors.full_messages).to include 'Rules[0]: for protocol "icmpv6" you cannot use IPv4 addresses'
1447+
end
1448+
end
1449+
1450+
context 'icmpv6 protocol contains an IPv4 destination range' do
1451+
let(:rules) do
1452+
[
1453+
{
1454+
protocol: 'icmpv6',
1455+
destination: '1.0.0.000-1.0.0.200',
1456+
type: -1,
1457+
code: 255
1458+
}
1459+
]
1460+
end
1461+
1462+
it 'is invalid' do
1463+
expect(subject).not_to be_valid
1464+
expect(subject.errors.full_messages).to include 'Rules[0]: for protocol "icmpv6" you cannot use IPv4 addresses'
1465+
end
1466+
end
1467+
1468+
context 'icmpv6 protocol contains a comma-delimited list of IPv4/IPv6 destinations' do
1469+
before do
1470+
TestConfig.config[:security_groups][:enable_comma_delimited_destinations] = true
1471+
end
1472+
1473+
let(:rules) do
1474+
[
1475+
{
1476+
protocol: 'icmpv6',
1477+
destination: '10.0.0.0/8,2001:db8::/32',
1478+
type: -1,
1479+
code: 255
1480+
}
1481+
]
1482+
end
1483+
1484+
it 'is invalid' do
1485+
expect(subject).not_to be_valid
1486+
expect(subject.errors.full_messages).to include 'Rules[0]: for protocol "icmpv6" you cannot use IPv4 addresses'
1487+
end
1488+
end
1489+
1490+
context 'the icmp rules are not provided when the protocol is icmpv6' do
1491+
let(:rules) do
1492+
[
1493+
{
1494+
protocol: 'icmpv6',
1495+
destination: '2001:db8::/32'
1496+
}
1497+
]
1498+
end
1499+
1500+
it 'is invalid' do
1501+
expect(subject).not_to be_valid
1502+
expect(subject.errors.full_messages).to include 'Rules[0]: type is required for protocols of type ICMP'
1503+
expect(subject.errors.full_messages).to include 'Rules[0]: code is required for protocols of type ICMP'
1504+
end
1505+
end
1506+
1507+
end
13061508
end
13071509
end
13081510
end

0 commit comments

Comments
 (0)