logstash-filter-elasticsearch 3.19.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,23 +2,13 @@
2
2
  require "logstash/filters/base"
3
3
  require "logstash/namespace"
4
4
  require "logstash/json"
5
- require 'logstash/plugin_mixins/ecs_compatibility_support'
6
- require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
7
5
  require 'logstash/plugin_mixins/ca_trusted_fingerprint_support'
8
- require "logstash/plugin_mixins/normalize_config_support"
9
- require 'logstash/plugin_mixins/validator_support/field_reference_validation_adapter'
10
6
  require "monitor"
11
7
 
12
8
  require_relative "elasticsearch/client"
9
+ require_relative "elasticsearch/patches/_elasticsearch_transport_http_manticore"
13
10
 
14
11
  class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
15
-
16
- require 'logstash/filters/elasticsearch/dsl_executor'
17
- require 'logstash/filters/elasticsearch/esql_executor'
18
-
19
- include LogStash::PluginMixins::ECSCompatibilitySupport
20
- include LogStash::PluginMixins::ECSCompatibilitySupport::TargetCheck
21
-
22
12
  config_name "elasticsearch"
23
13
 
24
14
  # List of elasticsearch hosts to use for querying.
@@ -28,13 +18,8 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
28
18
  # Field substitution (e.g. `index-name-%{date_field}`) is available
29
19
  config :index, :validate => :string, :default => ""
30
20
 
31
- # A type of Elasticsearch query, provided by @query.
32
- config :query_type, :validate => %w[esql dsl], :default => "dsl"
33
-
34
- # Elasticsearch query string. This can be in DSL or ES|QL query shape defined by @query_type.
35
- # Read the Elasticsearch query string documentation.
36
- # DSL: https://www.elastic.co/guide/en/elasticsearch/reference/master/query-dsl-query-string-query.html#query-string-syntax
37
- # ES|QL: https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html
21
+ # Elasticsearch query string. Read the Elasticsearch query string documentation.
22
+ # for more info at: https://www.elastic.co/guide/en/elasticsearch/reference/master/query-dsl-query-string-query.html#query-string-syntax
38
23
  config :query, :validate => :string
39
24
 
40
25
  # File path to elasticsearch query in DSL format. Read the Elasticsearch query documentation
@@ -47,9 +32,6 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
47
32
  # Array of fields to copy from old event (found via elasticsearch) into new event
48
33
  config :fields, :validate => :array, :default => {}
49
34
 
50
- # Custom headers for Elasticsearch requests
51
- config :custom_headers, :validate => :hash, :default => {}
52
-
53
35
  # Hash of docinfo fields to copy from old event (found via elasticsearch) into new event
54
36
  config :docinfo_fields, :validate => :hash, :default => {}
55
37
 
@@ -79,18 +61,6 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
79
61
  # Set the address of a forward HTTP proxy.
80
62
  config :proxy, :validate => :uri_or_empty
81
63
 
82
- # SSL
83
- config :ssl, :validate => :boolean, :default => false, :deprecated => "Set 'ssl_enabled' instead."
84
-
85
- # SSL Certificate Authority file
86
- config :ca_file, :validate => :path, :deprecated => "Set 'ssl_certificate_authorities' instead."
87
-
88
- # The keystore used to present a certificate to the server.
89
- # It can be either .jks or .p12
90
- config :keystore, :validate => :path, :deprecated => "Use 'ssl_keystore_path' instead."
91
-
92
- # Set the keystore password
93
- config :keystore_password, :validate => :password, :deprecated => "Use 'ssl_keystore_password' instead."
94
64
 
95
65
  # OpenSSL-style X.509 certificate certificate to authenticate the client
96
66
  config :ssl_certificate, :validate => :path
@@ -146,36 +116,24 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
146
116
  # Tags the event on failure to look up geo information. This can be used in later analysis.
147
117
  config :tag_on_failure, :validate => :array, :default => ["_elasticsearch_lookup_failure"]
148
118
 
149
- # If set, the result set will be nested under the target field
150
- config :target, :validate => :field_reference
151
-
152
119
  # How many times to retry on failure?
153
120
  config :retry_on_failure, :validate => :number, :default => 0
154
121
 
155
122
  # What status codes to retry on?
156
123
  config :retry_on_status, :validate => :number, :list => true, :default => [500, 502, 503, 504]
157
124
 
158
- # named placeholders in ES|QL query
159
- # example,
160
- # if the query is "FROM my-index | WHERE some_type = ?type AND depth > ?min_depth"
161
- # named placeholders can be applied as the following in query_params:
162
- # query_params => [
163
- # {"type" => "%{[type]}"}
164
- # {"min_depth" => "%{[depth]}"}
165
- # ]
166
- config :query_params, :validate => :array, :default => []
125
+
126
+ config :ssl, :obsolete => "Set 'ssl_enabled' instead."
127
+ config :ca_file, :obsolete => "Set 'ssl_certificate_authorities' instead."
128
+ config :keystore, :obsolete => "Set 'ssl_keystore_path' instead."
129
+ config :keystore_password, :validate => :password, :obsolete => "Set 'ssl_keystore_password' instead."
167
130
 
168
131
  # config :ca_trusted_fingerprint, :validate => :sha_256_hex
169
132
  include LogStash::PluginMixins::CATrustedFingerprintSupport
170
133
 
171
- include LogStash::PluginMixins::NormalizeConfigSupport
172
-
173
134
  include MonitorMixin
174
135
  attr_reader :shared_client
175
136
 
176
- LS_ESQL_SUPPORT_VERSION = "8.17.4" # the version started using elasticsearch-ruby v8
177
- ES_ESQL_SUPPORT_VERSION = "8.11.0"
178
-
179
137
  ##
180
138
  # @override to handle proxy => '' as if none was set
181
139
  # @param value [Array<Object>]
@@ -193,22 +151,17 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
193
151
  return super(value, :uri)
194
152
  end
195
153
 
196
- attr_reader :query_dsl
197
-
198
154
  def register
199
- case @query_type
200
- when "esql"
201
- invalid_params_with_esql = original_params.keys & %w(index query_template sort fields docinfo_fields aggregation_fields enable_sort result_size)
202
- raise LogStash::ConfigurationError, "Configured #{invalid_params_with_esql} params cannot be used with ES|QL query" if invalid_params_with_esql.any?
203
-
204
- validate_ls_version_for_esql_support!
205
- validate_esql_query_and_params!
206
- @esql_executor ||= LogStash::Filters::Elasticsearch::EsqlExecutor.new(self, @logger)
207
- else # dsl
208
- validate_dsl_query_settings!
209
- @esql_executor ||= LogStash::Filters::Elasticsearch::DslExecutor.new(self, @logger)
155
+ #Load query if it exists
156
+ if @query_template
157
+ if File.zero?(@query_template)
158
+ raise "template is empty"
159
+ end
160
+ file = File.open(@query_template, 'r')
161
+ @query_dsl = file.read
210
162
  end
211
163
 
164
+ validate_query_settings
212
165
  fill_hosts_from_cloud_id
213
166
  setup_ssl_params!
214
167
  validate_authentication
@@ -217,23 +170,73 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
217
170
  @hosts = Array(@hosts).map { |host| host.to_s } # potential SafeURI#to_s
218
171
 
219
172
  test_connection!
220
- validate_es_for_esql_support! if @query_type == "esql"
221
173
  setup_serverless
222
- if get_client.es_transport_client_type == "elasticsearch_transport"
223
- require_relative "elasticsearch/patches/_elasticsearch_transport_http_manticore"
224
- end
225
174
  end # def register
226
175
 
227
176
  def filter(event)
228
- @esql_executor.process(get_client, event)
229
- end # def filter
177
+ matched = false
178
+ begin
179
+ params = { :index => event.sprintf(@index) }
180
+
181
+ if @query_dsl
182
+ query = LogStash::Json.load(event.sprintf(@query_dsl))
183
+ params[:body] = query
184
+ else
185
+ query = event.sprintf(@query)
186
+ params[:q] = query
187
+ params[:size] = result_size
188
+ params[:sort] = @sort if @enable_sort
189
+ end
230
190
 
231
- def decorate(event)
232
- # this Elasticsearch class has access to `filter_matched`
233
- filter_matched(event)
234
- end
191
+ @logger.debug("Querying elasticsearch for lookup", :params => params)
192
+
193
+ results = get_client.search(params)
194
+ raise "Elasticsearch query error: #{results["_shards"]["failures"]}" if results["_shards"].include? "failures"
195
+
196
+ event.set("[@metadata][total_hits]", extract_total_from_hits(results['hits']))
197
+
198
+ resultsHits = results["hits"]["hits"]
199
+ if !resultsHits.nil? && !resultsHits.empty?
200
+ matched = true
201
+ @fields.each do |old_key, new_key|
202
+ old_key_path = extract_path(old_key)
203
+ set = resultsHits.map do |doc|
204
+ extract_value(doc["_source"], old_key_path)
205
+ end
206
+ event.set(new_key, set.count > 1 ? set : set.first)
207
+ end
208
+ @docinfo_fields.each do |old_key, new_key|
209
+ old_key_path = extract_path(old_key)
210
+ set = resultsHits.map do |doc|
211
+ extract_value(doc, old_key_path)
212
+ end
213
+ event.set(new_key, set.count > 1 ? set : set.first)
214
+ end
215
+ end
216
+
217
+ resultsAggs = results["aggregations"]
218
+ if !resultsAggs.nil? && !resultsAggs.empty?
219
+ matched = true
220
+ @aggregation_fields.each do |agg_name, ls_field|
221
+ event.set(ls_field, resultsAggs[agg_name])
222
+ end
223
+ end
224
+
225
+ rescue => e
226
+ if @logger.trace?
227
+ @logger.warn("Failed to query elasticsearch for previous event", :index => @index, :query => query, :event => event.to_hash, :error => e.message, :backtrace => e.backtrace)
228
+ elsif @logger.debug?
229
+ @logger.warn("Failed to query elasticsearch for previous event", :index => @index, :error => e.message, :backtrace => e.backtrace)
230
+ else
231
+ @logger.warn("Failed to query elasticsearch for previous event", :index => @index, :error => e.message)
232
+ end
233
+ @tag_on_failure.each{|tag| event.tag(tag)}
234
+ else
235
+ filter_matched(event) if matched
236
+ end
237
+ end # def filter
235
238
 
236
- # public only to be reused in testing
239
+ # public only to be reuse in testing
237
240
  def prepare_user_agent
238
241
  os_name = java.lang.System.getProperty('os.name')
239
242
  os_version = java.lang.System.getProperty('os.version')
@@ -257,8 +260,7 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
257
260
  :ssl => client_ssl_options,
258
261
  :retry_on_failure => @retry_on_failure,
259
262
  :retry_on_status => @retry_on_status,
260
- :user_agent => prepare_user_agent,
261
- :custom_headers => @custom_headers
263
+ :user_agent => prepare_user_agent
262
264
  }
263
265
  end
264
266
 
@@ -344,10 +346,53 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
344
346
  end
345
347
  end
346
348
 
349
+ # get an array of path elements from a path reference
350
+ def extract_path(path_reference)
351
+ return [path_reference] unless path_reference.start_with?('[') && path_reference.end_with?(']')
352
+
353
+ path_reference[1...-1].split('][')
354
+ end
355
+
356
+ # given a Hash and an array of path fragments, returns the value at the path
357
+ # @param source [Hash{String=>Object}]
358
+ # @param path [Array{String}]
359
+ # @return [Object]
360
+ def extract_value(source, path)
361
+ path.reduce(source) do |memo, old_key_fragment|
362
+ break unless memo.include?(old_key_fragment)
363
+ memo[old_key_fragment]
364
+ end
365
+ end
366
+
367
+ # Given a "hits" object from an Elasticsearch response, return the total number of hits in
368
+ # the result set.
369
+ # @param hits [Hash{String=>Object}]
370
+ # @return [Integer]
371
+ def extract_total_from_hits(hits)
372
+ total = hits['total']
373
+
374
+ # Elasticsearch 7.x produces an object containing `value` and `relation` in order
375
+ # to enable unambiguous reporting when the total is only a lower bound; if we get
376
+ # an object back, return its `value`.
377
+ return total['value'] if total.kind_of?(Hash)
378
+
379
+ total
380
+ end
381
+
347
382
  def hosts_default?(hosts)
348
383
  hosts.is_a?(Array) && hosts.size == 1 && !original_params.key?('hosts')
349
384
  end
350
385
 
386
+ def validate_query_settings
387
+ unless @query || @query_template
388
+ raise LogStash::ConfigurationError, "Both `query` and `query_template` are empty. Require either `query` or `query_template`."
389
+ end
390
+
391
+ if @query && @query_template
392
+ raise LogStash::ConfigurationError, "Both `query` and `query_template` are set. Use either `query` or `query_template`."
393
+ end
394
+ end
395
+
351
396
  def validate_authentication
352
397
  authn_options = 0
353
398
  authn_options += 1 if @cloud_auth
@@ -434,107 +479,9 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
434
479
  end
435
480
 
436
481
  def setup_ssl_params!
437
- @ssl_enabled = normalize_config(:ssl_enabled) do |normalize|
438
- normalize.with_deprecated_alias(:ssl)
439
- end
440
-
441
- # Infer the value if neither the deprecate `ssl` and `ssl_enabled` were set
442
- infer_ssl_enabled_from_hosts
443
-
444
- @ssl_keystore_path = normalize_config(:ssl_keystore_path) do |normalize|
445
- normalize.with_deprecated_alias(:keystore)
446
- end
447
-
448
- @ssl_keystore_password = normalize_config(:ssl_keystore_password) do |normalize|
449
- normalize.with_deprecated_alias(:keystore_password)
450
- end
451
-
452
- @ssl_certificate_authorities = normalize_config(:ssl_certificate_authorities) do |normalize|
453
- normalize.with_deprecated_mapping(:ca_file) do |ca_file|
454
- [ca_file]
455
- end
456
- end
457
-
458
- params['ssl_enabled'] = @ssl_enabled
459
- params['ssl_keystore_path'] = @ssl_keystore_path unless @ssl_keystore_path.nil?
460
- params['ssl_keystore_password'] = @ssl_keystore_password unless @ssl_keystore_password.nil?
461
- params['ssl_certificate_authorities'] = @ssl_certificate_authorities unless @ssl_certificate_authorities.nil?
462
- end
463
-
464
- def infer_ssl_enabled_from_hosts
465
- return if original_params.include?('ssl') || original_params.include?('ssl_enabled')
466
-
467
- @ssl_enabled = params['ssl_enabled'] = effectively_ssl?
468
- end
469
-
470
- def effectively_ssl?
471
- return true if @ssl_enabled
472
-
473
- hosts = Array(@hosts)
474
- return false if hosts.nil? || hosts.empty?
475
-
476
- hosts.all? { |host| host && host.to_s.start_with?("https") }
477
- end
478
-
479
- def validate_dsl_query_settings!
480
- #Load query if it exists
481
- if @query_template
482
- if File.zero?(@query_template)
483
- raise "template is empty"
484
- end
485
- file = File.open(@query_template, 'r')
486
- @query_dsl = file.read
487
- end
488
-
489
- validate_query_settings
490
- end
491
-
492
- def validate_query_settings
493
- unless @query || @query_template
494
- raise LogStash::ConfigurationError, "Both `query` and `query_template` are empty. Require either `query` or `query_template`."
495
- end
496
-
497
- if @query && @query_template
498
- raise LogStash::ConfigurationError, "Both `query` and `query_template` are set. Use either `query` or `query_template`."
499
- end
500
-
501
- if original_params.keys.include?("query_params")
502
- raise LogStash::ConfigurationError, "`query_params` is not allowed when `query_type => 'dsl'`."
503
- end
504
- end
505
-
506
- def validate_ls_version_for_esql_support!
507
- if Gem::Version.create(LOGSTASH_VERSION) < Gem::Version.create(LS_ESQL_SUPPORT_VERSION)
508
- fail("Current version of Logstash does not include Elasticsearch client which supports ES|QL. Please upgrade Logstash to at least #{LS_ESQL_SUPPORT_VERSION}")
509
- end
510
- end
511
-
512
- def validate_esql_query_and_params!
513
- # If Array, validate that query_params needs to contain only single-entry hashes, convert it to a Hash
514
- if @query_params.kind_of?(Array)
515
- illegal_entries = @query_params.reject {|e| e.kind_of?(Hash) && e.size == 1 }
516
- raise LogStash::ConfigurationError, "`query_params` must contain only single-entry hashes. Illegal placeholders: #{illegal_entries}" if illegal_entries.any?
517
-
518
- @query_params = @query_params.reduce({}, :merge)
519
- end
520
-
521
- illegal_keys = @query_params.keys.reject {|k| k[/^[a-z_][a-z0-9_]*$/] }
522
- if illegal_keys.any?
523
- message = "Illegal #{illegal_keys} placeholder names in `query_params`. A valid parameter name starts with a letter and contains letters, digits and underscores only;"
524
- raise LogStash::ConfigurationError, message
525
- end
526
-
527
- placeholders = @query.scan(/(?<=[?])[a-z_][a-z0-9_]*/i)
528
- placeholders.each do |placeholder|
529
- raise LogStash::ConfigurationError, "Placeholder #{placeholder} not found in query" unless @query_params.include?(placeholder)
530
- end
531
- end
532
-
533
- def validate_es_for_esql_support!
534
- # make sure connected ES supports ES|QL (8.11+)
535
- @es_version ||= get_client.es_version
536
- es_supports_esql = Gem::Version.create(@es_version) >= Gem::Version.create(ES_ESQL_SUPPORT_VERSION)
537
- fail("Connected Elasticsearch #{@es_version} version does not supports ES|QL. ES|QL feature requires at least Elasticsearch #{ES_ESQL_SUPPORT_VERSION} version.") unless es_supports_esql
482
+ # Infer the value if neither `ssl_enabled` was not set
483
+ return if original_params.include?('ssl_enabled')
484
+ params['ssl_enabled'] = @ssl_enabled ||= Array(@hosts).all? { |host| host && host.to_s.start_with?("https") }
538
485
  end
539
486
 
540
487
  end #class LogStash::Filters::Elasticsearch
@@ -1,13 +1,13 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-elasticsearch'
4
- s.version = '3.19.0'
4
+ s.version = '4.0.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Copies fields from previous log events in Elasticsearch to current events "
7
7
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
8
8
  s.authors = ["Elastic"]
9
9
  s.email = 'info@elastic.co'
10
- s.homepage = "https://elastic.co/logstash"
10
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
11
11
  s.require_paths = ["lib"]
12
12
 
13
13
  # Files
@@ -21,12 +21,9 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # Gem dependencies
23
23
  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
- s.add_runtime_dependency 'elasticsearch', ">= 7.14.9", '< 9'
24
+ s.add_runtime_dependency 'elasticsearch', ">= 7.14.9" # LS >= 6.7 and < 7.14 all used version 5.0.5
25
25
  s.add_runtime_dependency 'manticore', ">= 0.7.1"
26
- s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~> 1.3'
27
26
  s.add_runtime_dependency 'logstash-mixin-ca_trusted_fingerprint_support', '~> 1.0'
28
- s.add_runtime_dependency 'logstash-mixin-normalize_config_support', '~>1.0'
29
- s.add_runtime_dependency 'logstash-mixin-validator_support', '~> 1.0'
30
27
  s.add_development_dependency 'cabin', ['~> 0.6']
31
28
  s.add_development_dependency 'webrick'
32
29
  s.add_development_dependency 'logstash-devutils'