Skip to content
Open
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
7 changes: 5 additions & 2 deletions app/actions/route_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@ def validation_error_quota!(error, space)
end

def validation_error_route!(error)
return unless error.errors.on(:route)&.include?(:hash_header_missing)
error!('Hash header must be present when loadbalancing is set to hash.') if error.errors.on(:route)&.include?(:hash_header_missing)

error!('Hash header must be present when loadbalancing is set to hash.')
return unless error.errors.on(:route)&.include?(:options_size_exceeded)

max_size = Config.config.get(:max_route_options_size)
error!("Route options size exceeded: options must be smaller than #{max_size} bytes.")
end

def validation_error_routing_api!(error)
Expand Down
6 changes: 6 additions & 0 deletions app/actions/route_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def validation_error!(error)
# Handle hash_header validation error for hash loadbalancing
raise Error.new('Hash header must be present when loadbalancing is set to hash.') if error.errors.on(:route)&.include?(:hash_header_missing)

# Handle route options size exceeded error
if error.errors.on(:route)&.include?(:options_size_exceeded)
max_size = Config.config.get(:max_route_options_size)
raise Error.new("Route options size exceeded: options must be smaller than #{max_size} bytes.")
end

# Fallback for any other validation errors
raise Error.new(error.message)
end
Expand Down
13 changes: 13 additions & 0 deletions app/messages/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ def validate(record)
return
end

validate_size(record)

opt = record.options_message

return if opt.valid?
Expand All @@ -255,6 +257,17 @@ def validate(record)
record.errors.add(:options, message:)
end
end

private

def validate_size(record)
max_size = VCAP::CloudController::Config.config.get(:max_route_options_size)
options_size = record.options.to_json.bytesize

return unless options_size > max_size

record.errors.add(:options, message: "must be smaller than #{max_size} bytes (actual size is #{options_size} bytes)")
end
end

class ToOneRelationshipValidator < ActiveModel::EachValidator
Expand Down
14 changes: 14 additions & 0 deletions app/models/runtime/route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,20 @@ def validate_total_reserved_route_ports
def validate_route_options
return if options.blank?

validate_route_options_size
validate_route_options_hash_header
end

def validate_route_options_size
max_size = Config.config.get(:max_route_options_size)
options_size = options.to_json.bytesize

return unless options_size > max_size

errors.add(:route, :options_size_exceeded)
end

def validate_route_options_hash_header
route_options = options.is_a?(Hash) ? options : options.symbolize_keys
loadbalancing = route_options[:loadbalancing] || route_options['loadbalancing']

Expand Down
1 change: 1 addition & 0 deletions config/cloud_controller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ routing_api:
route_services_enabled: true
volume_services_enabled: true
disable_private_domain_cross_space_context_path_route_sharing: false
max_route_options_size: 1024

quota_definitions:
default:
Expand Down
1 change: 1 addition & 0 deletions lib/cloud_controller/config_schemas/api_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ApiSchema < VCAP::Config
optional(:system_domain_organization) => enum(String, NilClass),
app_domains: Array,
disable_private_domain_cross_space_context_path_route_sharing: bool,
max_route_options_size: Integer,

cpu_weight_min_memory: Integer,
cpu_weight_max_memory: Integer,
Expand Down
1 change: 1 addition & 0 deletions lib/cloud_controller/config_schemas/worker_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class WorkerSchema < VCAP::Config
external_protocol: String,
internal_service_hostname: String,
disable_private_domain_cross_space_context_path_route_sharing: bool,
max_route_options_size: Integer,
readiness_port: {
cloud_controller_worker: Integer
},
Expand Down
31 changes: 31 additions & 0 deletions spec/unit/actions/route_create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,37 @@ module VCAP::CloudController
end.to raise_error(RouteCreate::Error, 'Hash header must be present when loadbalancing is set to hash.')
end
end

context 'when options size exceeds the configured limit' do
before do
TestConfig.override(max_route_options_size: 50)
# Enable hash_based_routing feature to allow hash_header in options
VCAP::CloudController::FeatureFlag.make(name: 'hash_based_routing', enabled: true, error_message: nil)
end

let(:message_with_large_options) do
RouteCreateMessage.new({
relationships: {
space: {
data: { guid: space.guid }
},
domain: {
data: { guid: domain.guid }
}
},
options: {
loadbalancing: 'hash',
hash_header: 'X-Custom-Header-Name'
}
})
end

it 'raises an error indicating options size exceeded' do
expect do
subject.create(message: message_with_large_options, space: space, domain: domain)
end.to raise_error(RouteCreate::Error, /Route options size exceeded: options must be smaller than 50 bytes/)
end
end
end

context 'when creating a route with other loadbalancing options' do
Expand Down
24 changes: 24 additions & 0 deletions spec/unit/actions/route_update_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,30 @@ module VCAP::CloudController
end
end
end

context 'when options size exceeds the configured limit' do
before do
TestConfig.override(max_route_options_size: 50)
# Enable hash_based_routing feature to allow hash_header in options
VCAP::CloudController::FeatureFlag.make(name: 'hash_based_routing', enabled: true, error_message: nil)
route[:options] = '{"loadbalancing": "round-robin"}'
end

let(:body) do
{
options: {
loadbalancing: 'hash',
hash_header: 'a' * 100
}
}
end

it 'raises an error indicating options size exceeded' do
expect do
subject.update(route:, message:)
end.to raise_error(RouteUpdate::Error, /Route options size exceeded: options must be smaller than 50 bytes/)
end
end
end
end
end
43 changes: 43 additions & 0 deletions spec/unit/messages/route_create_message_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,49 @@ module VCAP::CloudController
end
end
end

context 'when options size exceeds the configured limit' do
before do
TestConfig.override(max_route_options_size: 50)
end

let(:params) do
{
host: 'some-host',
relationships: {
space: { data: { guid: 'space-guid' } },
domain: { data: { guid: 'domain-guid' } }
},
options: { loadbalancing: 'round-robin', some_large_key: 'a' * 100 }
}
end

it 'is not valid' do
expect(subject).not_to be_valid
expect(subject.errors[:options].to_s).to include('must be smaller than 50 bytes')
end
end

context 'when options size is within the configured limit' do
before do
TestConfig.override(max_route_options_size: 1024)
end

let(:params) do
{
host: 'some-host',
relationships: {
space: { data: { guid: 'space-guid' } },
domain: { data: { guid: 'domain-guid' } }
},
options: { loadbalancing: 'round-robin' }
}
end

it 'is valid' do
expect(subject).to be_valid
end
end
end
end

Expand Down
37 changes: 37 additions & 0 deletions spec/unit/models/runtime/route_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,43 @@ module VCAP::CloudController
end
end

context 'when options size exceeds the configured limit' do
before do
TestConfig.override(max_route_options_size: 50)
end

it 'is invalid and adds an error' do
large_options = { loadbalancing: 'round-robin', some_large_key: 'a' * 100 }
route = Route.new(
host: 'test-route',
domain: domain,
space: space,
options: large_options
)

expect(route).not_to be_valid
expect(route.errors[:route]).to include :options_size_exceeded
end
end

context 'when options size is within the configured limit' do
before do
TestConfig.override(max_route_options_size: 1024)
end

it 'is valid' do
small_options = { loadbalancing: 'round-robin' }
route = Route.new(
host: 'test-route',
domain: domain,
space: space,
options: small_options
)

expect(route).to be_valid
end
end

context 'when updating an existing route' do
context 'changing to hash loadbalancing without hash_header' do
it 'is invalid' do
Expand Down
Loading