logstash-output-elasticsearch-test 11.16.0-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +649 -0
  3. data/CONTRIBUTORS +34 -0
  4. data/Gemfile +16 -0
  5. data/LICENSE +202 -0
  6. data/NOTICE.TXT +5 -0
  7. data/README.md +106 -0
  8. data/docs/index.asciidoc +1369 -0
  9. data/lib/logstash/outputs/elasticsearch/data_stream_support.rb +282 -0
  10. data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
  11. data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +155 -0
  12. data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +534 -0
  13. data/lib/logstash/outputs/elasticsearch/http_client.rb +497 -0
  14. data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +201 -0
  15. data/lib/logstash/outputs/elasticsearch/ilm.rb +92 -0
  16. data/lib/logstash/outputs/elasticsearch/license_checker.rb +52 -0
  17. data/lib/logstash/outputs/elasticsearch/template_manager.rb +131 -0
  18. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-6x.json +45 -0
  19. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-7x.json +44 -0
  20. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-8x.json +50 -0
  21. data/lib/logstash/outputs/elasticsearch.rb +699 -0
  22. data/lib/logstash/plugin_mixins/elasticsearch/api_configs.rb +237 -0
  23. data/lib/logstash/plugin_mixins/elasticsearch/common.rb +409 -0
  24. data/lib/logstash/plugin_mixins/elasticsearch/noop_license_checker.rb +9 -0
  25. data/logstash-output-elasticsearch.gemspec +40 -0
  26. data/spec/es_spec_helper.rb +225 -0
  27. data/spec/fixtures/_nodes/6x.json +81 -0
  28. data/spec/fixtures/_nodes/7x.json +92 -0
  29. data/spec/fixtures/htpasswd +2 -0
  30. data/spec/fixtures/license_check/active.json +16 -0
  31. data/spec/fixtures/license_check/inactive.json +5 -0
  32. data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
  33. data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
  34. data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
  35. data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
  36. data/spec/fixtures/template-with-policy-es6x.json +48 -0
  37. data/spec/fixtures/template-with-policy-es7x.json +45 -0
  38. data/spec/fixtures/template-with-policy-es8x.json +50 -0
  39. data/spec/fixtures/test_certs/ca.crt +29 -0
  40. data/spec/fixtures/test_certs/ca.der.sha256 +1 -0
  41. data/spec/fixtures/test_certs/ca.key +51 -0
  42. data/spec/fixtures/test_certs/renew.sh +13 -0
  43. data/spec/fixtures/test_certs/test.crt +30 -0
  44. data/spec/fixtures/test_certs/test.der.sha256 +1 -0
  45. data/spec/fixtures/test_certs/test.key +51 -0
  46. data/spec/fixtures/test_certs/test.p12 +0 -0
  47. data/spec/fixtures/test_certs/test_invalid.crt +36 -0
  48. data/spec/fixtures/test_certs/test_invalid.key +51 -0
  49. data/spec/fixtures/test_certs/test_invalid.p12 +0 -0
  50. data/spec/fixtures/test_certs/test_self_signed.crt +32 -0
  51. data/spec/fixtures/test_certs/test_self_signed.key +54 -0
  52. data/spec/fixtures/test_certs/test_self_signed.p12 +0 -0
  53. data/spec/integration/outputs/compressed_indexing_spec.rb +70 -0
  54. data/spec/integration/outputs/create_spec.rb +67 -0
  55. data/spec/integration/outputs/data_stream_spec.rb +68 -0
  56. data/spec/integration/outputs/delete_spec.rb +63 -0
  57. data/spec/integration/outputs/ilm_spec.rb +534 -0
  58. data/spec/integration/outputs/index_spec.rb +421 -0
  59. data/spec/integration/outputs/index_version_spec.rb +98 -0
  60. data/spec/integration/outputs/ingest_pipeline_spec.rb +75 -0
  61. data/spec/integration/outputs/metrics_spec.rb +66 -0
  62. data/spec/integration/outputs/no_es_on_startup_spec.rb +78 -0
  63. data/spec/integration/outputs/painless_update_spec.rb +99 -0
  64. data/spec/integration/outputs/parent_spec.rb +94 -0
  65. data/spec/integration/outputs/retry_spec.rb +182 -0
  66. data/spec/integration/outputs/routing_spec.rb +61 -0
  67. data/spec/integration/outputs/sniffer_spec.rb +94 -0
  68. data/spec/integration/outputs/templates_spec.rb +133 -0
  69. data/spec/integration/outputs/unsupported_actions_spec.rb +75 -0
  70. data/spec/integration/outputs/update_spec.rb +114 -0
  71. data/spec/spec_helper.rb +10 -0
  72. data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
  73. data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
  74. data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
  75. data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
  76. data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
  77. data/spec/unit/http_client_builder_spec.rb +185 -0
  78. data/spec/unit/outputs/elasticsearch/data_stream_support_spec.rb +612 -0
  79. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +151 -0
  80. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +501 -0
  81. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +339 -0
  82. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +189 -0
  83. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +103 -0
  84. data/spec/unit/outputs/elasticsearch_spec.rb +1573 -0
  85. data/spec/unit/outputs/elasticsearch_ssl_spec.rb +197 -0
  86. data/spec/unit/outputs/error_whitelist_spec.rb +56 -0
  87. data/spec/unit/outputs/license_check_spec.rb +57 -0
  88. metadata +423 -0
@@ -0,0 +1,497 @@
1
+ require "logstash/outputs/elasticsearch"
2
+ require 'logstash/outputs/elasticsearch/http_client/pool'
3
+ require 'logstash/outputs/elasticsearch/http_client/manticore_adapter'
4
+ require 'cgi'
5
+ require 'zlib'
6
+ require 'stringio'
7
+ require 'java'
8
+
9
+ module LogStash; module Outputs; class ElasticSearch;
10
+ # This is a constant instead of a config option because
11
+ # there really isn't a good reason to configure it.
12
+ #
13
+ # The criteria used are:
14
+ # 1. We need a number that's less than 100MiB because ES
15
+ # won't accept bulks larger than that.
16
+ # 2. It must be large enough to amortize the connection constant
17
+ # across multiple requests.
18
+ # 3. It must be small enough that even if multiple threads hit this size
19
+ # we won't use a lot of heap.
20
+ #
21
+ # We wound up agreeing that a number greater than 10 MiB and less than 100MiB
22
+ # made sense. We picked one on the lowish side to not use too much heap.
23
+ TARGET_BULK_BYTES = 20 * 1024 * 1024 # 20MiB
24
+
25
+ class HttpClient
26
+ attr_reader :client, :options, :logger, :pool, :action_count, :recv_count
27
+ # This is here in case we use DEFAULT_OPTIONS in the future
28
+ # DEFAULT_OPTIONS = {
29
+ # :setting => value
30
+ # }
31
+
32
+ #
33
+ # The `options` is a hash where the following symbol keys have meaning:
34
+ #
35
+ # * `:hosts` - array of String. Set a list of hosts to use for communication.
36
+ # * `:port` - number. set the port to use to communicate with Elasticsearch
37
+ # * `:user` - String. The user to use for authentication.
38
+ # * `:password` - String. The password to use for authentication.
39
+ # * `:timeout` - Float. A duration value, in seconds, after which a socket
40
+ # operation or request will be aborted if not yet successfull
41
+ # * `:client_settings` - a hash; see below for keys.
42
+ #
43
+ # The `client_settings` key is a has that can contain other settings:
44
+ #
45
+ # * `:ssl` - Boolean. Enable or disable SSL/TLS.
46
+ # * `:proxy` - String. Choose a HTTP HTTProxy to use.
47
+ # * `:path` - String. The leading path for prefixing Elasticsearch
48
+ # * `:headers` - Hash. Pairs of headers and their values
49
+ # requests. This is sometimes used if you are proxying Elasticsearch access
50
+ # through a special http path, such as using mod_rewrite.
51
+ def initialize(options={})
52
+ @logger = options[:logger]
53
+ @metric = options[:metric]
54
+ @bulk_request_metrics = @metric.namespace(:bulk_requests)
55
+ @bulk_response_metrics = @bulk_request_metrics.namespace(:responses)
56
+
57
+ # Again, in case we use DEFAULT_OPTIONS in the future, uncomment this.
58
+ # @options = DEFAULT_OPTIONS.merge(options)
59
+ @options = options
60
+
61
+ @url_template = build_url_template
62
+
63
+ @pool = build_pool(@options)
64
+ # mutex to prevent requests and sniffing to access the
65
+ # connection pool at the same time
66
+ @bulk_path = @options[:bulk_path]
67
+ end
68
+
69
+ def build_url_template
70
+ {
71
+ :scheme => self.scheme,
72
+ :user => self.user,
73
+ :password => self.password,
74
+ :host => "URLTEMPLATE",
75
+ :port => self.port,
76
+ :path => self.path
77
+ }
78
+ end
79
+
80
+ def template_install(template_endpoint, name, template, force=false)
81
+ if template_exists?(template_endpoint, name) && !force
82
+ @logger.debug("Found existing Elasticsearch template, skipping template management", name: name)
83
+ return
84
+ end
85
+ template_put(template_endpoint, name, template)
86
+ end
87
+
88
+ def last_es_version
89
+ @pool.last_es_version
90
+ end
91
+
92
+ def maximum_seen_major_version
93
+ @pool.maximum_seen_major_version
94
+ end
95
+
96
+ def serverless?
97
+ @pool.serverless?
98
+ end
99
+
100
+ def alive_urls_count
101
+ @pool.alive_urls_count
102
+ end
103
+
104
+ def bulk(actions)
105
+ @action_count ||= 0
106
+ @action_count += actions.size
107
+
108
+ return if actions.empty?
109
+
110
+ bulk_actions = actions.collect do |action, args, source|
111
+ args, source = update_action_builder(args, source) if action == 'update'
112
+
113
+ if source && action != 'delete'
114
+ next [ { action => args }, source ]
115
+ else
116
+ next { action => args }
117
+ end
118
+ end
119
+
120
+ body_stream = StringIO.new
121
+ if http_compression
122
+ body_stream.set_encoding "BINARY"
123
+ stream_writer = gzip_writer(body_stream)
124
+ else
125
+ stream_writer = body_stream
126
+ end
127
+
128
+ bulk_responses = []
129
+ batch_actions = []
130
+ bulk_actions.each_with_index do |action, index|
131
+ as_json = action.is_a?(Array) ?
132
+ action.map {|line| LogStash::Json.dump(line)}.join("\n") :
133
+ LogStash::Json.dump(action)
134
+ as_json << "\n"
135
+ if (stream_writer.pos + as_json.bytesize) > TARGET_BULK_BYTES && stream_writer.pos > 0
136
+ stream_writer.flush # ensure writer has sync'd buffers before reporting sizes
137
+ logger.debug("Sending partial bulk request for batch with one or more actions remaining.",
138
+ :action_count => batch_actions.size,
139
+ :payload_size => stream_writer.pos,
140
+ :content_length => body_stream.size,
141
+ :batch_offset => (index + 1 - batch_actions.size))
142
+ bulk_responses << bulk_send(body_stream, batch_actions)
143
+ body_stream.truncate(0) && body_stream.seek(0)
144
+ stream_writer = gzip_writer(body_stream) if http_compression
145
+ batch_actions.clear
146
+ end
147
+ stream_writer.write(as_json)
148
+ batch_actions << action
149
+ end
150
+
151
+ stream_writer.close if http_compression
152
+
153
+ logger.debug("Sending final bulk request for batch.",
154
+ :action_count => batch_actions.size,
155
+ :payload_size => stream_writer.pos,
156
+ :content_length => body_stream.size,
157
+ :batch_offset => (actions.size - batch_actions.size))
158
+ bulk_responses << bulk_send(body_stream, batch_actions) if body_stream.size > 0
159
+
160
+ body_stream.close if !http_compression
161
+ join_bulk_responses(bulk_responses)
162
+ end
163
+
164
+ def gzip_writer(io)
165
+ fail(ArgumentError, "Cannot create gzip writer on IO with unread bytes") unless io.eof?
166
+ fail(ArgumentError, "Cannot create gzip writer on non-empty IO") unless io.pos == 0
167
+
168
+ Zlib::GzipWriter.new(io, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY)
169
+ end
170
+
171
+ def join_bulk_responses(bulk_responses)
172
+ {
173
+ "errors" => bulk_responses.any? {|r| r["errors"] == true},
174
+ "items" => bulk_responses.reduce([]) {|m,r| m.concat(r.fetch("items", []))}
175
+ }
176
+ end
177
+
178
+ def bulk_send(body_stream, batch_actions)
179
+ params = http_compression ? {:headers => {"Content-Encoding" => "gzip"}} : {}
180
+ response = @pool.post(@bulk_path, params, body_stream.string)
181
+
182
+ @bulk_response_metrics.increment(response.code.to_s)
183
+
184
+ case response.code
185
+ when 200 # OK
186
+ LogStash::Json.load(response.body)
187
+ when 413 # Payload Too Large
188
+ logger.warn("Bulk request rejected: `413 Payload Too Large`", :action_count => batch_actions.size, :content_length => body_stream.size)
189
+ emulate_batch_error_response(batch_actions, response.code, 'payload_too_large')
190
+ else
191
+ url = ::LogStash::Util::SafeURI.new(response.final_url)
192
+ raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(
193
+ response.code, url, body_stream.to_s, response.body
194
+ )
195
+ end
196
+ end
197
+
198
+ def emulate_batch_error_response(actions, http_code, reason)
199
+ {
200
+ "errors" => true,
201
+ "items" => actions.map do |action|
202
+ action = action.first if action.is_a?(Array)
203
+ request_action, request_parameters = action.first
204
+ {
205
+ request_action => {"status" => http_code, "error" => { "type" => reason }}
206
+ }
207
+ end
208
+ }
209
+ end
210
+
211
+ def get(path)
212
+ response = @pool.get(path, nil)
213
+ LogStash::Json.load(response.body)
214
+ end
215
+
216
+ def post(path, params = {}, body_string)
217
+ response = @pool.post(path, params, body_string)
218
+ LogStash::Json.load(response.body)
219
+ end
220
+
221
+ def close
222
+ @pool.close
223
+ end
224
+
225
+ def calculate_property(uris, property, default, sniff_check)
226
+ values = uris.map(&property).uniq
227
+
228
+ if sniff_check && values.size > 1
229
+ raise LogStash::ConfigurationError, "Cannot have multiple values for #{property} in hosts when sniffing is enabled!"
230
+ end
231
+
232
+ uri_value = values.first
233
+
234
+ default = nil if default.is_a?(String) && default.empty? # Blanks are as good as nil
235
+ uri_value = nil if uri_value.is_a?(String) && uri_value.empty?
236
+
237
+ if default && uri_value && (default != uri_value)
238
+ raise LogStash::ConfigurationError, "Explicit value for '#{property}' was declared, but it is different in one of the URLs given! Please make sure your URLs are inline with explicit values. The URLs have the property set to '#{uri_value}', but it was also set to '#{default}' explicitly"
239
+ end
240
+
241
+ uri_value || default
242
+ end
243
+
244
+ def sniffing
245
+ @options[:sniffing]
246
+ end
247
+
248
+ def user
249
+ calculate_property(uris, :user, @options[:user], sniffing)
250
+ end
251
+
252
+ def password
253
+ calculate_property(uris, :password, @options[:password], sniffing)
254
+ end
255
+
256
+ def path
257
+ calculated = calculate_property(uris, :path, client_settings[:path], sniffing)
258
+ calculated = "/#{calculated}" if calculated && !calculated.start_with?("/")
259
+ calculated
260
+ end
261
+
262
+ def scheme
263
+ explicit_scheme = if ssl_options && ssl_options.has_key?(:enabled)
264
+ ssl_options[:enabled] ? 'https' : 'http'
265
+ else
266
+ nil
267
+ end
268
+
269
+ calculated_scheme = calculate_property(uris, :scheme, explicit_scheme, sniffing)
270
+
271
+ if calculated_scheme && calculated_scheme !~ /https?/
272
+ raise LogStash::ConfigurationError, "Bad scheme '#{calculated_scheme}' found should be one of http/https"
273
+ end
274
+
275
+ if calculated_scheme && explicit_scheme && calculated_scheme != explicit_scheme
276
+ raise LogStash::ConfigurationError, "SSL option was explicitly set to #{ssl_options[:enabled]} but a URL was also declared with a scheme of '#{explicit_scheme}'. Please reconcile this"
277
+ end
278
+
279
+ calculated_scheme # May be nil if explicit_scheme is nil!
280
+ end
281
+
282
+ def port
283
+ # We don't set the 'default' here because the default is what the user
284
+ # indicated, so we use an || outside of calculate_property. This lets people
285
+ # Enter things like foo:123, bar and wind up with foo:123, bar:9200
286
+ calculate_property(uris, :port, nil, sniffing) || 9200
287
+ end
288
+
289
+ def uris
290
+ @options[:hosts]
291
+ end
292
+
293
+ def client_settings
294
+ @_client_settings ||= @options[:client_settings] || {}
295
+ end
296
+
297
+ def ssl_options
298
+ @_ssl_options ||= client_settings.fetch(:ssl, {})
299
+ end
300
+
301
+ def http_compression
302
+ client_settings.fetch(:http_compression, false)
303
+ end
304
+
305
+ def build_adapter(options)
306
+ timeout = options[:timeout] || 0
307
+
308
+ adapter_options = {
309
+ :socket_timeout => timeout,
310
+ :request_timeout => timeout,
311
+ }
312
+
313
+ adapter_options[:user_agent] = prepare_user_agent
314
+
315
+ adapter_options[:proxy] = client_settings[:proxy] if client_settings[:proxy]
316
+
317
+ adapter_options[:check_connection_timeout] = client_settings[:check_connection_timeout] if client_settings[:check_connection_timeout]
318
+
319
+ # Having this explicitly set to nil is an error
320
+ if client_settings[:pool_max]
321
+ adapter_options[:pool_max] = client_settings[:pool_max]
322
+ end
323
+
324
+ # Having this explicitly set to nil is an error
325
+ if client_settings[:pool_max_per_route]
326
+ adapter_options[:pool_max_per_route] = client_settings[:pool_max_per_route]
327
+ end
328
+
329
+ adapter_options[:ssl] = ssl_options if self.scheme == 'https'
330
+
331
+ adapter_options[:headers] = client_settings[:headers] if client_settings[:headers]
332
+
333
+ ::LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter.new(@logger, adapter_options)
334
+ end
335
+
336
+ def prepare_user_agent
337
+ os_name = java.lang.System.getProperty('os.name')
338
+ os_version = java.lang.System.getProperty('os.version')
339
+ os_arch = java.lang.System.getProperty('os.arch')
340
+ jvm_vendor = java.lang.System.getProperty('java.vendor')
341
+ jvm_version = java.lang.System.getProperty('java.version')
342
+
343
+ plugin_version = Gem.loaded_specs['logstash-output-elasticsearch'].version
344
+ # example: Logstash/7.14.1 (OS=Linux-5.4.0-84-generic-amd64; JVM=AdoptOpenJDK-11.0.11) logstash-output-elasticsearch/11.0.1
345
+ "Logstash/#{LOGSTASH_VERSION} (OS=#{os_name}-#{os_version}-#{os_arch}; JVM=#{jvm_vendor}-#{jvm_version}) logstash-output-elasticsearch/#{plugin_version}"
346
+ end
347
+
348
+ def build_pool(options)
349
+ adapter = build_adapter(options)
350
+
351
+ pool_options = {
352
+ :license_checker => options[:license_checker],
353
+ :sniffing => sniffing,
354
+ :sniffer_delay => options[:sniffer_delay],
355
+ :sniffing_path => options[:sniffing_path],
356
+ :healthcheck_path => options[:healthcheck_path],
357
+ :resurrect_delay => options[:resurrect_delay],
358
+ :url_normalizer => self.method(:host_to_url),
359
+ :metric => options[:metric]
360
+ }
361
+ pool_options[:scheme] = self.scheme if self.scheme
362
+
363
+ pool_class = ::LogStash::Outputs::ElasticSearch::HttpClient::Pool
364
+ full_urls = @options[:hosts].map {|h| host_to_url(h) }
365
+ pool = pool_class.new(@logger, adapter, full_urls, pool_options)
366
+ pool.start
367
+ pool
368
+ end
369
+
370
+ def host_to_url(h)
371
+ # Never override the calculated scheme
372
+ raw_scheme = @url_template[:scheme] || 'http'
373
+
374
+ raw_user = h.user || @url_template[:user]
375
+ raw_password = h.password || @url_template[:password]
376
+ postfixed_userinfo = raw_user && raw_password ? "#{raw_user}:#{raw_password}@" : nil
377
+
378
+ raw_host = h.host # Always replace this!
379
+ raw_port = h.port || @url_template[:port]
380
+
381
+ raw_path = !h.path.nil? && !h.path.empty? && h.path != "/" ? h.path : @url_template[:path]
382
+ prefixed_raw_path = raw_path && !raw_path.empty? ? raw_path : "/"
383
+
384
+ parameters = client_settings[:parameters]
385
+ raw_query = if parameters && !parameters.empty?
386
+ combined = h.query ?
387
+ Hash[URI::decode_www_form(h.query)].merge(parameters) :
388
+ parameters
389
+ query_str = combined.flat_map {|k,v|
390
+ values = Array(v)
391
+ values.map {|av| "#{k}=#{av}"}
392
+ }.join("&")
393
+ query_str
394
+ else
395
+ h.query
396
+ end
397
+ prefixed_raw_query = raw_query && !raw_query.empty? ? "?#{raw_query}" : nil
398
+
399
+ raw_url = "#{raw_scheme}://#{postfixed_userinfo}#{raw_host}:#{raw_port}#{prefixed_raw_path}#{prefixed_raw_query}"
400
+
401
+ ::LogStash::Util::SafeURI.new(raw_url)
402
+ end
403
+
404
+ def exists?(path, use_get=false)
405
+ response = use_get ? @pool.get(path) : @pool.head(path)
406
+ response.code >= 200 && response.code <= 299
407
+ end
408
+
409
+ def template_exists?(template_endpoint, name)
410
+ exists?("/#{template_endpoint}/#{name}")
411
+ end
412
+
413
+ def template_put(template_endpoint, name, template)
414
+ path = "#{template_endpoint}/#{name}"
415
+ logger.info("Installing Elasticsearch template", name: name)
416
+ @pool.put(path, nil, LogStash::Json.dump(template))
417
+ end
418
+
419
+ # ILM methods
420
+
421
+ # check whether rollover alias already exists
422
+ def rollover_alias_exists?(name)
423
+ exists?(name)
424
+ end
425
+
426
+ # Create a new rollover alias
427
+ def rollover_alias_put(alias_name, alias_definition)
428
+ begin
429
+ @pool.put(CGI::escape(alias_name), nil, LogStash::Json.dump(alias_definition))
430
+ logger.info("Created rollover alias", name: alias_name)
431
+ # If the rollover alias already exists, ignore the error that comes back from Elasticsearch
432
+ rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
433
+ if e.response_code == 400
434
+ logger.info("Rollover alias already exists, skipping", name: alias_name)
435
+ return
436
+ end
437
+ raise e
438
+ end
439
+ end
440
+
441
+ def get_xpack_info
442
+ get("/_xpack")
443
+ end
444
+
445
+ def get_ilm_endpoint
446
+ @pool.get("/_ilm/policy")
447
+ end
448
+
449
+ def ilm_policy_exists?(name)
450
+ exists?("/_ilm/policy/#{name}", true)
451
+ end
452
+
453
+ def ilm_policy_put(name, policy)
454
+ path = "_ilm/policy/#{name}"
455
+ logger.info("Installing ILM policy #{policy}", name: name)
456
+ @pool.put(path, nil, LogStash::Json.dump(policy))
457
+ end
458
+
459
+
460
+ # Build a bulk item for an elasticsearch update action
461
+ def update_action_builder(args, source)
462
+ args = args.clone()
463
+ if args[:_script]
464
+ # Use the event as a hash from your script with variable name defined
465
+ # by script_var_name (default: "event")
466
+ # Ex: event["@timestamp"]
467
+ source_orig = source
468
+ source = { 'script' => {'params' => { @options[:script_var_name] => source_orig }} }
469
+ if @options[:scripted_upsert]
470
+ source['scripted_upsert'] = true
471
+ source['upsert'] = {}
472
+ elsif @options[:doc_as_upsert]
473
+ source['upsert'] = source_orig
474
+ else
475
+ source['upsert'] = args.delete(:_upsert) if args[:_upsert]
476
+ end
477
+ case @options[:script_type]
478
+ when 'indexed'
479
+ source['script']['id'] = args.delete(:_script)
480
+ when 'file'
481
+ source['script']['file'] = args.delete(:_script)
482
+ when 'inline'
483
+ source['script']['inline'] = args.delete(:_script)
484
+ end
485
+ source['script']['lang'] = @options[:script_lang] if @options[:script_lang] != ''
486
+ else
487
+ source = { 'doc' => source }
488
+ if @options[:doc_as_upsert]
489
+ source['doc_as_upsert'] = true
490
+ else
491
+ source['upsert'] = args.delete(:_upsert) if args[:_upsert]
492
+ end
493
+ end
494
+ [args, source]
495
+ end
496
+ end
497
+ end end end
@@ -0,0 +1,201 @@
1
+ require 'cgi'
2
+ require "base64"
3
+
4
+ module LogStash; module Outputs; class ElasticSearch;
5
+ module HttpClientBuilder
6
+ def self.build(logger, hosts, params)
7
+ client_settings = {
8
+ :pool_max => params["pool_max"],
9
+ :pool_max_per_route => params["pool_max_per_route"],
10
+ :check_connection_timeout => params["validate_after_inactivity"],
11
+ :http_compression => params["http_compression"],
12
+ :headers => params["custom_headers"] || {}
13
+ }
14
+
15
+ client_settings[:proxy] = params["proxy"] if params["proxy"]
16
+
17
+ common_options = {
18
+ :license_checker => params["license_checker"],
19
+ :client_settings => client_settings,
20
+ :metric => params["metric"],
21
+ :resurrect_delay => params["resurrect_delay"]
22
+ }
23
+
24
+ if params["sniffing"]
25
+ common_options[:sniffing] = true
26
+ common_options[:sniffer_delay] = params["sniffing_delay"]
27
+ end
28
+
29
+ common_options[:timeout] = params["timeout"] if params["timeout"]
30
+
31
+ if params["path"]
32
+ client_settings[:path] = dedup_slashes("/#{params["path"]}/")
33
+ end
34
+
35
+ common_options[:bulk_path] = if params["bulk_path"]
36
+ dedup_slashes("/#{params["bulk_path"]}")
37
+ else
38
+ dedup_slashes("/#{params["path"]}/_bulk")
39
+ end
40
+
41
+ common_options[:sniffing_path] = if params["sniffing_path"]
42
+ dedup_slashes("/#{params["sniffing_path"]}")
43
+ else
44
+ dedup_slashes("/#{params["path"]}/_nodes/http")
45
+ end
46
+
47
+ common_options[:healthcheck_path] = if params["healthcheck_path"]
48
+ dedup_slashes("/#{params["healthcheck_path"]}")
49
+ else
50
+ dedup_slashes("/#{params["path"]}")
51
+ end
52
+
53
+ if params["parameters"]
54
+ client_settings[:parameters] = params["parameters"]
55
+ end
56
+
57
+ logger.debug? && logger.debug("Normalizing http path", :path => params["path"], :normalized => client_settings[:path])
58
+
59
+ client_settings.merge! setup_ssl(logger, params)
60
+ common_options.merge! setup_basic_auth(logger, params)
61
+ client_settings[:headers].merge! setup_api_key(logger, params)
62
+
63
+ external_version_types = ["external", "external_gt", "external_gte"]
64
+ # External Version validation
65
+ raise(
66
+ LogStash::ConfigurationError,
67
+ "External versioning requires the presence of a version number."
68
+ ) if external_version_types.include?(params.fetch('version_type', '')) and params.fetch("version", nil) == nil
69
+
70
+
71
+ # Create API setup
72
+ raise(
73
+ LogStash::ConfigurationError,
74
+ "External versioning is not supported by the create action."
75
+ ) if params['action'] == 'create' and external_version_types.include?(params.fetch('version_type', ''))
76
+
77
+ # Update API setup
78
+ raise( LogStash::ConfigurationError,
79
+ "doc_as_upsert and scripted_upsert are mutually exclusive."
80
+ ) if params["doc_as_upsert"] and params["scripted_upsert"]
81
+
82
+ raise(
83
+ LogStash::ConfigurationError,
84
+ "Specifying action => 'update' needs a document_id."
85
+ ) if params['action'] == 'update' and params.fetch('document_id', '') == ''
86
+
87
+ raise(
88
+ LogStash::ConfigurationError,
89
+ "External versioning is not supported by the update action. See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html."
90
+ ) if params['action'] == 'update' and external_version_types.include?(params.fetch('version_type', ''))
91
+
92
+ # Update API setup
93
+ update_options = {
94
+ :doc_as_upsert => params["doc_as_upsert"],
95
+ :script_var_name => params["script_var_name"],
96
+ :script_type => params["script_type"],
97
+ :script_lang => params["script_lang"],
98
+ :scripted_upsert => params["scripted_upsert"]
99
+ }
100
+ common_options.merge! update_options if params["action"] == 'update'
101
+
102
+ create_http_client(common_options.merge(:hosts => hosts, :logger => logger))
103
+ end
104
+
105
+ def self.create_http_client(options)
106
+ LogStash::Outputs::ElasticSearch::HttpClient.new(options)
107
+ end
108
+
109
+ def self.setup_ssl(logger, params)
110
+ params["ssl_enabled"] = true if params["hosts"].any? {|h| h.scheme == "https" }
111
+ return {} if params["ssl_enabled"].nil?
112
+
113
+ return {:ssl => {:enabled => false}} if params["ssl_enabled"] == false
114
+
115
+ ssl_certificate_authorities, ssl_truststore_path, ssl_certificate, ssl_keystore_path = params.values_at('ssl_certificate_authorities', 'ssl_truststore_path', 'ssl_certificate', 'ssl_keystore_path')
116
+
117
+ if ssl_certificate_authorities && ssl_truststore_path
118
+ raise LogStash::ConfigurationError, 'Use either "ssl_certificate_authorities/cacert" or "ssl_truststore_path/truststore" when configuring the CA certificate'
119
+ end
120
+
121
+ if ssl_certificate && ssl_keystore_path
122
+ raise LogStash::ConfigurationError, 'Use either "ssl_certificate" or "ssl_keystore_path/keystore" when configuring client certificates'
123
+ end
124
+
125
+ ssl_options = {:enabled => true}
126
+
127
+ if ssl_certificate_authorities&.any?
128
+ raise LogStash::ConfigurationError, 'Multiple values on "ssl_certificate_authorities" are not supported by this plugin' if ssl_certificate_authorities.size > 1
129
+ ssl_options[:ca_file] = ssl_certificate_authorities.first
130
+ end
131
+
132
+ setup_ssl_store(ssl_options, 'truststore', params)
133
+ setup_ssl_store(ssl_options, 'keystore', params)
134
+
135
+ ssl_key = params["ssl_key"]
136
+ if ssl_certificate
137
+ raise LogStash::ConfigurationError, 'Using an "ssl_certificate" requires an "ssl_key"' unless ssl_key
138
+ ssl_options[:client_cert] = ssl_certificate
139
+ ssl_options[:client_key] = ssl_key
140
+ elsif !ssl_key.nil?
141
+ raise LogStash::ConfigurationError, 'An "ssl_certificate" is required when using an "ssl_key"'
142
+ end
143
+
144
+ ssl_verification_mode = params["ssl_verification_mode"]
145
+ unless ssl_verification_mode.nil?
146
+ case ssl_verification_mode
147
+ when 'none'
148
+ logger.warn "You have enabled encryption but DISABLED certificate verification, " +
149
+ "to make sure your data is secure set `ssl_verification_mode => full`"
150
+ ssl_options[:verify] = :disable
151
+ else
152
+ # Manticore's :default maps to Apache HTTP Client's DefaultHostnameVerifier,
153
+ # which is the modern STRICT verifier that replaces the deprecated StrictHostnameVerifier
154
+ ssl_options[:verify] = :default
155
+ end
156
+ end
157
+
158
+ ssl_options[:cipher_suites] = params["ssl_cipher_suites"] if params.include?("ssl_cipher_suites")
159
+ ssl_options[:trust_strategy] = params["ssl_trust_strategy"] if params.include?("ssl_trust_strategy")
160
+
161
+ protocols = params['ssl_supported_protocols']
162
+ ssl_options[:protocols] = protocols if protocols && protocols.any?
163
+
164
+ { ssl: ssl_options }
165
+ end
166
+
167
+ # @param kind is a string [truststore|keystore]
168
+ def self.setup_ssl_store(ssl_options, kind, params)
169
+ store_path = params["ssl_#{kind}_path"]
170
+ if store_path
171
+ ssl_options[kind.to_sym] = store_path
172
+ ssl_options["#{kind}_type".to_sym] = params["ssl_#{kind}_type"] if params.include?("ssl_#{kind}_type")
173
+ ssl_options["#{kind}_password".to_sym] = params["ssl_#{kind}_password"].value if params.include?("ssl_#{kind}_password")
174
+ end
175
+ end
176
+
177
+ def self.setup_basic_auth(logger, params)
178
+ user, password = params["user"], params["password"]
179
+
180
+ return {} unless user && password && password.value
181
+
182
+ {
183
+ :user => CGI.escape(user),
184
+ :password => CGI.escape(password.value)
185
+ }
186
+ end
187
+
188
+ def self.setup_api_key(logger, params)
189
+ api_key = params["api_key"]
190
+
191
+ return {} unless (api_key && api_key.value)
192
+
193
+ { "Authorization" => "ApiKey " + Base64.strict_encode64(api_key.value) }
194
+ end
195
+
196
+ private
197
+ def self.dedup_slashes(url)
198
+ url.gsub(/\/+/, "/")
199
+ end
200
+ end
201
+ end; end; end