Skip to content

Add support for IPv6 in security groups #4318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions app/messages/validators/security_group_rule_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ def validate(record)
add_rule_error("protocol must be 'tcp', 'udp', 'icmp', or 'all'", record, index) unless valid_protocol(rule[:protocol])

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

rules.each do |d|
destinations.each do |d|
validate_destination(d, record, index)
end
end
Expand Down Expand Up @@ -142,12 +142,17 @@ def validate_destination(destination, record, index)
add_rule_error(error_message, record, index) unless CloudController::RuleValidator.parse_ip(address_list.first)

elsif address_list.length == 2
ipv4s = CloudController::RuleValidator.parse_ip(address_list)
return add_rule_error('destination IP address range is invalid', record, index) unless ipv4s
ips = CloudController::RuleValidator.parse_ip(address_list)
return add_rule_error('destination IP address range is invalid', record, index) unless ips

sorted_ips = if ips.first.is_a?(NetAddr::IPv4)
NetAddr.sort_IPv4(ips)
else
NetAddr.sort_IPv6(ips)
end

sorted_ipv4s = NetAddr.sort_IPv4(ipv4s)
reversed_range_error = 'beginning of IP address range is numerically greater than the end of its range (range endpoints are inverted)'
add_rule_error(reversed_range_error, record, index) unless ipv4s.first == sorted_ipv4s.first
add_rule_error(reversed_range_error, record, index) unless ips.first == sorted_ips.first

else
add_rule_error(error_message, record, index)
Expand Down
4 changes: 3 additions & 1 deletion lib/cloud_controller/config_schemas/base/api_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ class ApiSchema < VCAP::Config
},

update_metric_tags_on_rename: bool,
app_instance_stopping_state: bool
app_instance_stopping_state: bool,

optional(:enable_ipv6) => bool
}
end
# rubocop:enable Metrics/BlockLength
Expand Down
75 changes: 61 additions & 14 deletions lib/cloud_controller/rule_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,16 @@ def self.validate_destination(destination)
return true if parse_ip(address_list.first)

elsif address_list.length == 2
ipv4s = parse_ip(address_list)
return false if ipv4s.nil?
ips = parse_ip(address_list)
return false if ips.nil?

sorted_ipv4s = NetAddr.sort_IPv4(ipv4s)
return true if ipv4s.first == sorted_ipv4s.first
sorted_ips = if ips.first.is_a?(NetAddr::IPv4)
NetAddr.sort_IPv4(ips)
else
NetAddr.sort_IPv6(ips)
end

return true if ips.first == sorted_ips.first
end

false
Expand All @@ -65,19 +70,14 @@ def self.validate_boolean(bool)
end

def self.parse_ip(val)
if val.is_a?(Array)
val.map do |ip|
NetAddr::IPv4.parse(ip)
end
else
NetAddr::IPv4Net.parse(val)
end
rescue NetAddr::ValidationError
nil
ipv4 = parse_ipv4(val)

ipv6 = parse_ipv6(val) if !ipv4 && config.get(:enable_ipv6)

ipv4 || ipv6
end

def self.comma_delimited_destinations_enabled?
config = VCAP::CloudController::Config.config
config.get(:security_groups, :enable_comma_delimited_destinations)
end

Expand All @@ -92,7 +92,17 @@ def self.no_leading_zeros(destination)
no_zeros
end

private_class_method def self.config
VCAP::CloudController::Config.config
end

private_class_method def self.no_leading_zeros_in_address(address)
return no_leading_zeros_in_ipv4_address(address) if address.include?('.')

no_leading_zeros_in_ipv6_address(address) if address.include?(':')
end

private_class_method def self.no_leading_zeros_in_ipv4_address(address)
address.split('.') do |octet|
if octet.start_with?('0') && octet.length > 1
octet_parts = octet.split('/')
Expand All @@ -104,5 +114,42 @@ def self.no_leading_zeros(destination)

true
end

private_class_method def self.no_leading_zeros_in_ipv6_address(address)
address.split(':').each do |segment|
next unless segment.start_with?('0') && segment.length > 1

segment_parts = segment.split('/')
return false if segment_parts.length < 2

return false if segment_parts[0].length > 1 && segment_parts[0].start_with?('0')
end

true
end

private_class_method def self.parse_ipv4(val)
if val.is_a?(Array)
val.map do |ip|
NetAddr::IPv4.parse(ip)
end
else
NetAddr::IPv4Net.parse(val)
end
rescue NetAddr::ValidationError
nil
end

private_class_method def self.parse_ipv6(val)
if val.is_a?(Array)
val.map do |ip|
NetAddr::IPv6.parse(ip)
end
else
NetAddr::IPv6Net.parse(val)
end
rescue NetAddr::ValidationError
nil
end
end
end
Loading
Loading