Skip to content

Commit

Permalink
Add system properties to configure Jackson's stream read constraints (#…
Browse files Browse the repository at this point in the history
…15720)

This commit added a few jvm.options properties to configure the Jackson read constraints defaults (Maximum Number value length, Maximum String value length, and Maximum Nesting depth).
  • Loading branch information
edmocosta authored Jan 8, 2024
1 parent 9f1d55c commit a21ced0
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 1 deletion.
20 changes: 19 additions & 1 deletion config/jvm.options
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,22 @@
-Djava.security.egd=file:/dev/urandom

# Copy the logging context from parent threads to children
-Dlog4j2.isThreadContextMapInheritable=true
-Dlog4j2.isThreadContextMapInheritable=true

# FasterXML/jackson defaults
#
# Sets the maximum string length (in chars or bytes, depending on input context).
# This limit is not exact and an exception will happen at sizes greater than this limit.
# Some text values that are a little bigger than the limit may be treated as valid but no
# text values with sizes less than or equal to this limit will be treated as invalid.
# This value should be higher than `logstash.jackson.stream-read-constraints.max-number-length`.
# The jackson library defaults to 20000000 or 20MB, whereas Logstash defaults to 200MB or 200000000 characters.
-Dlogstash.jackson.stream-read-constraints.max-string-length=200000000
#
# Sets the maximum number length (in chars or bytes, depending on input context).
# The jackson library defaults to 1000, whereas Logstash defaults to 10000.
-Dlogstash.jackson.stream-read-constraints.max-number-length=10000
#
# Sets the maximum nesting depth. The depth is a count of objects and arrays that have not
# been closed, `{` and `[` respectively.
#-Dlogstash.jackson.stream-read-constraints.max-nesting-depth=1000
4 changes: 4 additions & 0 deletions logstash-core/lib/logstash/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
require "logstash/environment"
require "logstash/modules/cli_parser"
require "logstash/util/settings_helper"
require "logstash/util/jackson"

LogStash::Environment.load_locale!

Expand Down Expand Up @@ -328,6 +329,9 @@ def execute
# Add local modules to the registry before everything else
LogStash::Modules::Util.register_local_modules(LogStash::Environment::LOGSTASH_HOME)

# Set up the Jackson defaults
LogStash::Util::Jackson.set_jackson_defaults(logger)

@dispatcher = LogStash::EventDispatcher.new(self)
LogStash::PLUGIN_REGISTRY.hooks.register_emitter(self.class, @dispatcher)

Expand Down
93 changes: 93 additions & 0 deletions logstash-core/lib/logstash/util/jackson.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module LogStash
module Util
module Jackson
def self.set_jackson_defaults(logger)
JacksonStreamReadConstraintsDefaults.new(logger).configure
end

class JacksonStreamReadConstraintsDefaults

java_import com.fasterxml.jackson.core.StreamReadConstraints

PROPERTY_MAX_STRING_LENGTH = 'logstash.jackson.stream-read-constraints.max-string-length'.freeze
PROPERTY_MAX_NUMBER_LENGTH = 'logstash.jackson.stream-read-constraints.max-number-length'.freeze
PROPERTY_MAX_NESTING_DEPTH = 'logstash.jackson.stream-read-constraints.max-nesting-depth'.freeze

def initialize(logger)
@logger = logger
end

public

def configure
max_string_len = get_default_value_override!(PROPERTY_MAX_STRING_LENGTH)
max_num_len = get_default_value_override!(PROPERTY_MAX_NUMBER_LENGTH)
max_nesting_depth = get_default_value_override!(PROPERTY_MAX_NESTING_DEPTH)

if max_string_len || max_num_len || max_nesting_depth
begin
override_default_stream_read_constraints(max_string_len, max_num_len, max_nesting_depth)
rescue java.lang.IllegalArgumentException => e
raise LogStash::ConfigurationError, "Invalid `logstash.jackson.*` system properties configuration: #{e.message}"
end
end
end

private

def get_default_value_override!(property)
value = get_property_value(property)
return if value.nil?

begin
int_value = java.lang.Integer.parseInt(value)

if int_value < 1
raise LogStash::ConfigurationError, "System property '#{property}' must be bigger than zero. Received: #{int_value}"
end

@logger.info("Jackson default value override `#{property}` configured to `#{int_value}`")

int_value
rescue java.lang.NumberFormatException => _e
raise LogStash::ConfigurationError, "System property '#{property}' must be a positive integer value. Received: #{value}"
end
end

def get_property_value(name)
java.lang.System.getProperty(name)
end

def override_default_stream_read_constraints(max_string_len, max_num_len, max_nesting_depth)
builder = new_stream_read_constraints_builder
builder.maxStringLength(max_string_len) if max_string_len
builder.maxNumberLength(max_num_len) if max_num_len
builder.maxNestingDepth(max_nesting_depth) if max_nesting_depth

StreamReadConstraints.overrideDefaultStreamReadConstraints(builder.build)
end

def new_stream_read_constraints_builder
StreamReadConstraints::builder
end
end
end
end
end
10 changes: 10 additions & 0 deletions logstash-core/spec/logstash/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,16 @@
end
end

describe 'jackson defaults' do
subject { LogStash::Runner.new("") }
let(:args) { ["-e", "input {} output {}"] }

it 'should be set' do
expect(LogStash::Util::Jackson).to receive(:set_jackson_defaults)
subject.run(args)
end
end

describe "--log.level" do
before :each do
allow_any_instance_of(subject).to receive(:show_version)
Expand Down
113 changes: 113 additions & 0 deletions logstash-core/spec/logstash/util/jackson_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

require 'spec_helper'

describe LogStash::Util::Jackson do
it 'configures the read constraints defaults' do
read_constraints_defaults = double('read_constraints_defaults')
expect(read_constraints_defaults).to receive(:configure)

expect(LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults).to receive(:new).and_return(read_constraints_defaults)

LogStash::Util::Jackson.set_jackson_defaults(double('logger').as_null_object)
end
end

describe LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults do
let(:logger) { double('logger') }

subject { described_class.new(logger) }

shared_examples 'stream read constraint property' do |property|
let(:property) { property }
let(:value) { nil }
let(:builder) { double('builder') }
let(:builder_set_value_method) { expected_builder_set_value_method(property) }

before(:each) do
allow(logger).to receive(:info)

allow(builder).to receive(:build).and_return(com.fasterxml.jackson.core.StreamReadConstraints::builder.build)
allow(builder).to receive(builder_set_value_method).with(value.to_i)

allow(subject).to receive(:new_stream_read_constraints_builder).and_return(builder)
allow(subject).to receive(:get_property_value) do |name|
if name == property
value.to_s
else
nil
end
end
end

context 'with valid number' do
let(:value) { '10' }
it 'does not raises an error and set value' do
expect { subject.configure }.to_not raise_error
expect(builder).to have_received(builder_set_value_method).with(value.to_i)
end
end

context 'with non-number value' do
let(:value) { 'foo' }
it 'raises an error and does not set value' do
expect { subject.configure }.to raise_error(LogStash::ConfigurationError, /System property '#{property}' must be a positive integer value. Received: #{value}/)
expect(builder).to_not have_received(builder_set_value_method)
end
end

context 'with zeroed value' do
let(:value) { '0' }
it 'raises an error and does not set value' do
expect { subject.configure }.to raise_error(LogStash::ConfigurationError, /System property '#{property}' must be bigger than zero. Received: #{value}/)
expect(builder).to_not have_received(builder_set_value_method)
end
end

context 'with zeroed value' do
let(:value) { '-1' }
it 'raises an error and does not set value' do
expect { subject.configure }.to raise_error(LogStash::ConfigurationError, /System property '#{property}' must be bigger than zero. Received: #{value}/)
expect(builder).to_not have_received(builder_set_value_method)
end
end

def expected_builder_set_value_method(property)
case property
when LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_STRING_LENGTH
return :maxStringLength
when LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NUMBER_LENGTH
return :maxNumberLength
when LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NESTING_DEPTH
return :maxNestingDepth
else
raise 'Invalid system property value'
end
end
end

[
LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_STRING_LENGTH,
LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NUMBER_LENGTH,
LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NESTING_DEPTH,
].each { |system_property|
context "#{system_property}" do
it_behaves_like "stream read constraint property", system_property
end
}
end

0 comments on commit a21ced0

Please sign in to comment.