logstash-input-opensearch 1.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.
@@ -0,0 +1,432 @@
1
+ # Copyright OpenSearch Contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # encoding: utf-8
5
+ require "logstash/inputs/base"
6
+ require "logstash/namespace"
7
+ require "logstash/json"
8
+ require "logstash/util/safe_uri"
9
+ require 'logstash/plugin_mixins/validator_support/field_reference_validation_adapter'
10
+ require 'logstash/plugin_mixins/event_support/event_factory_adapter'
11
+ require 'logstash/plugin_mixins/ecs_compatibility_support'
12
+ require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
13
+ require "base64"
14
+
15
+ require "opensearch"
16
+ require "opensearch/transport/transport/http/manticore"
17
+ require_relative "opensearch/patches/_opensearch_transport_http_manticore"
18
+ require_relative "opensearch/patches/_opensearch_transport_connections_selector"
19
+
20
+ # .Compatibility Note
21
+ # [NOTE]
22
+ # ================================================================================
23
+ # Starting with OpenSearch 5.3, there's an {ref}modules-http.html[HTTP setting]
24
+ # called `http.content_type.required`. If this option is set to `true`, and you
25
+ # are using Logstash 2.4 through 5.2, you need to update the OpenSearch input
26
+ # plugin to version 4.0.2 or higher.
27
+ #
28
+ # ================================================================================
29
+ #
30
+ # Read from an OpenSearch cluster, based on search query results.
31
+ # This is useful for replaying test logs, reindexing, etc.
32
+ # It also supports periodically scheduling lookup enrichments
33
+ # using a cron syntax (see `schedule` setting).
34
+ #
35
+ # Example:
36
+ # [source,ruby]
37
+ # input {
38
+ # # Read all documents from OpenSearch matching the given query
39
+ # opensearch {
40
+ # hosts => "localhost"
41
+ # query => '{ "query": { "match": { "statuscode": 200 } }, "sort": [ "_doc" ] }'
42
+ # }
43
+ # }
44
+ #
45
+ # This would create an OpenSearch query with the following format:
46
+ # [source,json]
47
+ # curl 'http://localhost:9200/logstash-*/_search?&scroll=1m&size=1000' -d '{
48
+ # "query": {
49
+ # "match": {
50
+ # "statuscode": 200
51
+ # }
52
+ # },
53
+ # "sort": [ "_doc" ]
54
+ # }'
55
+ #
56
+ # ==== Scheduling
57
+ #
58
+ # Input from this plugin can be scheduled to run periodically according to a specific
59
+ # schedule. This scheduling syntax is powered by https://github.com/jmettraux/rufus-scheduler[rufus-scheduler].
60
+ # The syntax is cron-like with some extensions specific to Rufus (e.g. timezone support ).
61
+ #
62
+ # Examples:
63
+ #
64
+ # |==========================================================
65
+ # | `* 5 * 1-3 *` | will execute every minute of 5am every day of January through March.
66
+ # | `0 * * * *` | will execute on the 0th minute of every hour every day.
67
+ # | `0 6 * * * America/Chicago` | will execute at 6:00am (UTC/GMT -5) every day.
68
+ # |==========================================================
69
+ #
70
+ #
71
+ # Further documentation describing this syntax can be found https://github.com/jmettraux/rufus-scheduler#parsing-cronlines-and-time-strings[here].
72
+ #
73
+ #
74
+ class LogStash::Inputs::OpenSearch < LogStash::Inputs::Base
75
+
76
+ include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
77
+ include LogStash::PluginMixins::ECSCompatibilitySupport::TargetCheck
78
+
79
+ include LogStash::PluginMixins::EventSupport::EventFactoryAdapter
80
+
81
+ extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
82
+
83
+ config_name "opensearch"
84
+
85
+ # List of opensearch hosts to use for querying.
86
+ # Each host can be either IP, HOST, IP:port or HOST:port.
87
+ # Port defaults to 9200
88
+ config :hosts, :validate => :array
89
+
90
+ # The index or alias to search.
91
+ config :index, :validate => :string, :default => "logstash-*"
92
+
93
+ # The query to be executed. Read the OpenSearch query DSL documentation
94
+ # for more info
95
+ # https://opensearch.org/docs/latest/opensearch/query-dsl/index/
96
+ config :query, :validate => :string, :default => '{ "sort": [ "_doc" ] }'
97
+
98
+ # This allows you to set the maximum number of hits returned per scroll.
99
+ config :size, :validate => :number, :default => 1000
100
+
101
+ # This parameter controls the keepalive time in seconds of the scrolling
102
+ # request and initiates the scrolling process. The timeout applies per
103
+ # round trip (i.e. between the previous scroll request, to the next).
104
+ config :scroll, :validate => :string, :default => "1m"
105
+
106
+ # This parameter controls the number of parallel slices to be consumed simultaneously
107
+ # by this pipeline input.
108
+ config :slices, :validate => :number
109
+
110
+ # If set, include OpenSearch document information such as index, type, and
111
+ # the id in the event.
112
+ #
113
+ # It might be important to note, with regards to metadata, that if you're
114
+ # ingesting documents with the intent to re-index them (or just update them)
115
+ # that the `action` option in the opensearch output wants to know how to
116
+ # handle those things. It can be dynamically assigned with a field
117
+ # added to the metadata.
118
+ #
119
+ # Example
120
+ # [source, ruby]
121
+ # input {
122
+ # opensearch {
123
+ # hosts => "es.production.mysite.org"
124
+ # index => "mydata-2018.09.*"
125
+ # query => "*"
126
+ # size => 500
127
+ # scroll => "5m"
128
+ # docinfo => true
129
+ # }
130
+ # }
131
+ # output {
132
+ # opensearch {
133
+ # index => "copy-of-production.%{[@metadata][_index]}"
134
+ # document_type => "%{[@metadata][_type]}"
135
+ # document_id => "%{[@metadata][_id]}"
136
+ # }
137
+ # }
138
+ #
139
+ config :docinfo, :validate => :boolean, :default => false
140
+
141
+ # Where to move the OpenSearch document information.
142
+ # default: [@metadata][input][opensearch] in ECS mode, @metadata field otherwise
143
+ config :docinfo_target, :validate=> :field_reference
144
+
145
+ # List of document metadata to move to the `docinfo_target` field.
146
+ config :docinfo_fields, :validate => :array, :default => ['_index', '_type', '_id']
147
+
148
+ # Basic Auth - username
149
+ config :user, :validate => :string
150
+
151
+ # Basic Auth - password
152
+ config :password, :validate => :password
153
+
154
+ # Connection Timeout, in Seconds
155
+ config :connect_timeout_seconds, :validate => :positive_whole_number, :default => 10
156
+
157
+ # Request Timeout, in Seconds
158
+ config :request_timeout_seconds, :validate => :positive_whole_number, :default => 60
159
+
160
+ # Socket Timeout, in Seconds
161
+ config :socket_timeout_seconds, :validate => :positive_whole_number, :default => 60
162
+
163
+ # Set the address of a forward HTTP proxy.
164
+ config :proxy, :validate => :uri_or_empty
165
+
166
+ # SSL
167
+ config :ssl, :validate => :boolean, :default => false
168
+
169
+ # SSL Certificate Authority file in PEM encoded format, must also include any chain certificates as necessary
170
+ config :ca_file, :validate => :path
171
+
172
+ # Schedule of when to periodically run statement, in Cron format
173
+ # for example: "* * * * *" (execute query every minute, on the minute)
174
+ #
175
+ # There is no schedule by default. If no schedule is given, then the statement is run
176
+ # exactly once.
177
+ config :schedule, :validate => :string
178
+
179
+ # If set, the _source of each hit will be added nested under the target instead of at the top-level
180
+ config :target, :validate => :field_reference
181
+
182
+ def initialize(params={})
183
+ super(params)
184
+
185
+ if docinfo_target.nil?
186
+ @docinfo_target = ecs_select[disabled: '@metadata', v1: '[@metadata][input][opensearch]']
187
+ end
188
+ end
189
+
190
+ def register
191
+ require "rufus/scheduler"
192
+
193
+ @options = {
194
+ :index => @index,
195
+ :scroll => @scroll,
196
+ :size => @size
197
+ }
198
+ @base_query = LogStash::Json.load(@query)
199
+ if @slices
200
+ @base_query.include?('slice') && fail(LogStash::ConfigurationError, "OpenSearch Input Plugin's `query` option cannot specify specific `slice` when configured to manage parallel slices with `slices` option")
201
+ @slices < 1 && fail(LogStash::ConfigurationError, "OpenSearch Input Plugin's `slices` option must be greater than zero, got `#{@slices}`")
202
+ end
203
+
204
+ transport_options = {:headers => {}}
205
+ transport_options[:headers].merge!(setup_basic_auth(user, password))
206
+ transport_options[:headers].merge!({'user-agent' => prepare_user_agent()})
207
+ transport_options[:request_timeout] = @request_timeout_seconds unless @request_timeout_seconds.nil?
208
+ transport_options[:connect_timeout] = @connect_timeout_seconds unless @connect_timeout_seconds.nil?
209
+ transport_options[:socket_timeout] = @socket_timeout_seconds unless @socket_timeout_seconds.nil?
210
+
211
+ hosts = setup_hosts
212
+ ssl_options = setup_ssl
213
+
214
+ @logger.warn "Supplied proxy setting (proxy => '') has no effect" if @proxy.eql?('')
215
+
216
+ transport_options[:proxy] = @proxy.to_s if @proxy && !@proxy.eql?('')
217
+
218
+ @client = OpenSearch::Client.new(
219
+ :hosts => hosts,
220
+ :transport_options => transport_options,
221
+ :transport_class => ::OpenSearch::Transport::Transport::HTTP::Manticore,
222
+ :ssl => ssl_options
223
+ )
224
+ test_connection!
225
+ @client
226
+ end
227
+
228
+
229
+ def run(output_queue)
230
+ if @schedule
231
+ @scheduler = Rufus::Scheduler.new(:max_work_threads => 1)
232
+ @scheduler.cron @schedule do
233
+ do_run(output_queue)
234
+ end
235
+
236
+ @scheduler.join
237
+ else
238
+ do_run(output_queue)
239
+ end
240
+ end
241
+
242
+ def stop
243
+ @scheduler.stop if @scheduler
244
+ end
245
+
246
+ private
247
+
248
+ def do_run(output_queue)
249
+ # if configured to run a single slice, don't bother spinning up threads
250
+ return do_run_slice(output_queue) if @slices.nil? || @slices <= 1
251
+
252
+ logger.warn("managed slices for query is very large (#{@slices}); consider reducing") if @slices > 8
253
+
254
+ @slices.times.map do |slice_id|
255
+ Thread.new do
256
+ LogStash::Util::set_thread_name("#{@id}_slice_#{slice_id}")
257
+ do_run_slice(output_queue, slice_id)
258
+ end
259
+ end.map(&:join)
260
+ end
261
+
262
+ def do_run_slice(output_queue, slice_id=nil)
263
+ slice_query = @base_query
264
+ slice_query = slice_query.merge('slice' => { 'id' => slice_id, 'max' => @slices}) unless slice_id.nil?
265
+
266
+ slice_options = @options.merge(:body => LogStash::Json.dump(slice_query) )
267
+
268
+ logger.info("Slice starting", slice_id: slice_id, slices: @slices) unless slice_id.nil?
269
+
270
+ begin
271
+ r = search_request(slice_options)
272
+
273
+ r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
274
+ logger.debug("Slice progress", slice_id: slice_id, slices: @slices) unless slice_id.nil?
275
+
276
+ has_hits = r['hits']['hits'].any?
277
+ scroll_id = r['_scroll_id']
278
+
279
+ while has_hits && scroll_id && !stop?
280
+ has_hits, scroll_id = process_next_scroll(output_queue, scroll_id)
281
+ logger.debug("Slice progress", slice_id: slice_id, slices: @slices) if logger.debug? && slice_id
282
+ end
283
+ logger.info("Slice complete", slice_id: slice_id, slices: @slices) unless slice_id.nil?
284
+ ensure
285
+ clear_scroll(scroll_id)
286
+ end
287
+ end
288
+
289
+ ##
290
+ # @param output_queue [#<<]
291
+ # @param scroll_id [String]: a scroll id to resume
292
+ # @return [Array(Boolean,String)]: a tuple representing whether the response
293
+ #
294
+ def process_next_scroll(output_queue, scroll_id)
295
+ r = scroll_request(scroll_id)
296
+ r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
297
+ [r['hits']['hits'].any?, r['_scroll_id']]
298
+ rescue => e
299
+ # this will typically be triggered by a scroll timeout
300
+ logger.error("Scroll request error, aborting scroll", message: e.message, exception: e.class)
301
+ # return no hits and original scroll_id so we can try to clear it
302
+ [false, scroll_id]
303
+ end
304
+
305
+ def push_hit(hit, output_queue)
306
+ event = targeted_event_factory.new_event hit['_source']
307
+ set_docinfo_fields(hit, event) if @docinfo
308
+ decorate(event)
309
+ output_queue << event
310
+ end
311
+
312
+ def set_docinfo_fields(hit, event)
313
+ # do not assume event[@docinfo_target] to be in-place updatable. first get it, update it, then at the end set it in the event.
314
+ docinfo_target = event.get(@docinfo_target) || {}
315
+
316
+ unless docinfo_target.is_a?(Hash)
317
+ @logger.error("Incompatible Event, incompatible type for the docinfo_target=#{@docinfo_target} field in the `_source` document, expected a hash got:", :docinfo_target_type => docinfo_target.class, :event => event.to_hash_with_metadata)
318
+
319
+ # TODO: (colin) I am not sure raising is a good strategy here?
320
+ raise Exception.new("OpenSearch input: incompatible event")
321
+ end
322
+
323
+ @docinfo_fields.each do |field|
324
+ docinfo_target[field] = hit[field]
325
+ end
326
+
327
+ event.set(@docinfo_target, docinfo_target)
328
+ end
329
+
330
+ def clear_scroll(scroll_id)
331
+ @client.clear_scroll(:body => { :scroll_id => scroll_id }) if scroll_id
332
+ rescue => e
333
+ # ignore & log any clear_scroll errors
334
+ logger.warn("Ignoring clear_scroll exception", message: e.message, exception: e.class)
335
+ end
336
+
337
+ def scroll_request scroll_id
338
+ @client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
339
+ end
340
+
341
+ def search_request(options)
342
+ @client.search(options)
343
+ end
344
+
345
+ def hosts_default?(hosts)
346
+ hosts.nil? || ( hosts.is_a?(Array) && hosts.empty? )
347
+ end
348
+
349
+ def setup_ssl
350
+ return { :ssl => true, :ca_file => @ca_file } if @ssl && @ca_file
351
+ return { :ssl => true, :verify => false } if @ssl # Setting verify as false if ca_file is not provided
352
+ end
353
+
354
+ def setup_hosts
355
+ @hosts = Array(@hosts).map { |host| host.to_s } # potential SafeURI#to_s
356
+ if @ssl
357
+ @hosts.map do |h|
358
+ host, port = h.split(":")
359
+ { :host => host, :scheme => 'https', :port => port }
360
+ end
361
+ else
362
+ @hosts
363
+ end
364
+ end
365
+
366
+ def setup_basic_auth(user, password)
367
+ return {} unless user && password && password.value
368
+
369
+ token = ::Base64.strict_encode64("#{user}:#{password.value}")
370
+ { 'Authorization' => "Basic #{token}" }
371
+ end
372
+
373
+ def prepare_user_agent
374
+ os_name = java.lang.System.getProperty('os.name')
375
+ os_version = java.lang.System.getProperty('os.version')
376
+ os_arch = java.lang.System.getProperty('os.arch')
377
+ jvm_vendor = java.lang.System.getProperty('java.vendor')
378
+ jvm_version = java.lang.System.getProperty('java.version')
379
+
380
+ plugin_version = Gem.loaded_specs["logstash-input-opensearch"].version
381
+ # example: logstash/7.14.1 (OS=Linux-5.4.0-84-generic-amd64; JVM=AdoptOpenJDK-11.0.11) logstash-input-opensearch/4.10.0
382
+ "logstash/#{LOGSTASH_VERSION} (OS=#{os_name}-#{os_version}-#{os_arch}; JVM=#{jvm_vendor}-#{jvm_version}) logstash-#{@plugin_type}-#{config_name}/#{plugin_version}"
383
+ end
384
+
385
+ # @private used by unit specs
386
+ attr_reader :client
387
+
388
+ def test_connection!
389
+ @client.ping
390
+ rescue OpenSearch::UnsupportedProductError
391
+ raise LogStash::ConfigurationError, "Could not connect to a compatible version of OpenSearch"
392
+ end
393
+
394
+ module URIOrEmptyValidator
395
+ ##
396
+ # @override to provide :uri_or_empty validator
397
+ # @param value [Array<Object>]
398
+ # @param validator [nil,Array,Symbol]
399
+ # @return [Array(true,Object)]: if validation is a success, a tuple containing `true` and the coerced value
400
+ # @return [Array(false,String)]: if validation is a failure, a tuple containing `false` and the failure reason.
401
+ def validate_value(value, validator)
402
+ return super unless validator == :uri_or_empty
403
+
404
+ value = deep_replace(value)
405
+ value = hash_or_array(value)
406
+
407
+ return true, value.first if value.size == 1 && value.first.empty?
408
+
409
+ return super(value, :uri)
410
+ end
411
+ end
412
+ extend(URIOrEmptyValidator)
413
+
414
+ module PositiveWholeNumberValidator
415
+ ##
416
+ # @override to provide :positive_whole_number validator
417
+ # @param value [Array<Object>]
418
+ # @param validator [nil,Array,Symbol]
419
+ # @return [Array(true,Object)]: if validation is a success, a tuple containing `true` and the coerced value
420
+ # @return [Array(false,String)]: if validation is a failure, a tuple containing `false` and the failure reason.
421
+ def validate_value(value, validator)
422
+ return super unless validator == :positive_whole_number
423
+
424
+ is_number, coerced_number = super(value, :number)
425
+
426
+ return [true, coerced_number.to_i] if is_number && coerced_number.denominator == 1 && coerced_number > 0
427
+
428
+ return [false, "Expected positive whole number, got `#{value.inspect}`"]
429
+ end
430
+ end
431
+ extend(PositiveWholeNumberValidator)
432
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright OpenSearch Contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ signing_key_path = "private_key.pem"
5
+
6
+ Gem::Specification.new do |s|
7
+
8
+ s.name = 'logstash-input-opensearch'
9
+ s.version = '1.0.0'
10
+ s.licenses = ['Apache License (2.0)']
11
+ s.summary = "Reads query results from an OpenSearch cluster"
12
+ 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"
13
+ s.authors = ["Elastic", "OpenSearch Contributors"]
14
+ s.email = 'opensearch@amazon.com'
15
+ s.homepage = "https://opensearch.org/"
16
+ s.require_paths = ["lib"]
17
+
18
+ # Files
19
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION", "docs/**/*"]
20
+
21
+ # Tests
22
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
23
+
24
+ if $PROGRAM_NAME.end_with?("gem") && ARGV == ["build", __FILE__] && File.exist?(signing_key_path)
25
+ s.signing_key = signing_key_path
26
+ s.cert_chain = ['certs/opensearch-rubygems.pem']
27
+ s.signing_key = File.expand_path("private_key.pem") if $0 =~ /gem\z/
28
+ end
29
+
30
+ # Special flag to let us know this is actually a logstash plugin
31
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
32
+
33
+ # Gem dependencies
34
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
35
+ s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~> 1.3'
36
+ s.add_runtime_dependency 'logstash-mixin-event_support', '~> 1.0'
37
+ s.add_runtime_dependency "logstash-mixin-validator_support", '~> 1.0'
38
+
39
+ s.add_runtime_dependency 'tzinfo'
40
+ s.add_runtime_dependency 'tzinfo-data'
41
+ s.add_runtime_dependency 'rufus-scheduler'
42
+ s.add_runtime_dependency 'manticore', ">= 0.7.1"
43
+
44
+ s.add_runtime_dependency 'opensearch-ruby'
45
+
46
+ s.add_development_dependency 'logstash-codec-plain'
47
+ s.add_development_dependency 'faraday', "~> 1"
48
+ s.add_development_dependency 'logstash-devutils'
49
+ s.add_development_dependency 'timecop'
50
+ s.add_development_dependency 'cabin', ['~> 0.6']
51
+ s.add_development_dependency 'webrick'
52
+ end
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDSTCCAjGgAwIBAgIUUcAg9c8B8jiliCkOEJyqoAHrmccwDQYJKoZIhvcNAQEL
3
+ BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
4
+ cmF0ZWQgQ0EwHhcNMjEwODEyMDUxNDU1WhcNMjQwODExMDUxNDU1WjA0MTIwMAYD
5
+ VQQDEylFbGFzdGljIENlcnRpZmljYXRlIFRvb2wgQXV0b2dlbmVyYXRlZCBDQTCC
6
+ ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1HuusRuGNsztd4EQvqwcMr
7
+ 8XvnNNaalerpMOorCGySEFrNf0HxDIVMGMCrOv1F8SvlcGq3XANs2MJ4F2xhhLZr
8
+ PpqVHx+QnSZ66lu5R89QVSuMh/dCMxhNBlOA/dDlvy+EJBl9H791UGy/ChhSgaBd
9
+ OKVyGkhjErRTeMIq7rR7UG6GL/fV+JGy41UiLrm1KQP7/XVD9UzZfGq/hylFkTPe
10
+ oox5BUxdxUdDZ2creOID+agtIYuJVIkelKPQ+ljBY3kWBRexqJQsvyNUs1gZpjpz
11
+ YUCzuVcXDRuJXYQXGqWXhsBPfJv+ZcSyMIBUfWT/G13cWU1iwufPy0NjajowPZsC
12
+ AwEAAaNTMFEwHQYDVR0OBBYEFMgkye5+2l+TE0I6RsXRHjGBwpBGMB8GA1UdIwQY
13
+ MBaAFMgkye5+2l+TE0I6RsXRHjGBwpBGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
14
+ hvcNAQELBQADggEBAIgtJW8sy5lBpzPRHkmWSS/SCZIPsABW+cHqQ3e0udrI3CLB
15
+ G9n7yqAPWOBTbdqC2GM8dvAS/Twx4Bub/lWr84dFCu+t0mQq4l5kpJMVRS0KKXPL
16
+ DwJbUN3oPNYy4uPn5Xi+XY3BYFce5vwJUsqIxeAbIOxVTNx++k5DFnB0ESAM23QL
17
+ sgUZl7xl3/DkdO4oHj30gmTRW9bjCJ6umnHIiO3JoJatrprurUIt80vHC4Ndft36
18
+ NBQ9mZpequ4RYjpSZNLcVsxyFAYwEY4g8MvH0MoMo2RRLfehmMCzXnI/Wh2qEyYz
19
+ emHprBii/5y1HieKXlX9CZRb5qEPHckDVXW3znw=
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEArUe66xG4Y2zO13gRC+rBwyvxe+c01pqV6ukw6isIbJIQWs1/
3
+ QfEMhUwYwKs6/UXxK+VwardcA2zYwngXbGGEtms+mpUfH5CdJnrqW7lHz1BVK4yH
4
+ 90IzGE0GU4D90OW/L4QkGX0fv3VQbL8KGFKBoF04pXIaSGMStFN4wirutHtQboYv
5
+ 99X4kbLjVSIuubUpA/v9dUP1TNl8ar+HKUWRM96ijHkFTF3FR0NnZyt44gP5qC0h
6
+ i4lUiR6Uo9D6WMFjeRYFF7GolCy/I1SzWBmmOnNhQLO5VxcNG4ldhBcapZeGwE98
7
+ m/5lxLIwgFR9ZP8bXdxZTWLC58/LQ2NqOjA9mwIDAQABAoIBABmBC0P6Ebegljkk
8
+ lO26GdbOKvbfqulDS3mN5QMyXkUMopea03YzMnKUJriE+2O33a1mUcuDPWnLpYPK
9
+ BTiQieYHlulNtY0Bzf+R69igRq9+1WpZftGnzrlu7NVxkOokRqWJv3546ilV7QZ0
10
+ f9ngmu+tiN7hEnlBC8m613VMuGGb3czwbCizEVZxlZX0Dk2GExbH7Yf3NNs/aOP/
11
+ 8x6CqgL+rhrtOQ80xwRrOlEF8oSSjXCzypa3nFv21YO3J2lVo4BoIwnHgOzyz46A
12
+ b37gekqXXajIYQ0HAB+NDgVoCRFFJ7Xe16mgB3DpyUpUJzwiMedJkeQ0TprIownQ
13
+ +1mPe9ECgYEA/K4jc0trr3sk8KtcZjOYdpvwrhEqSSGEPeGfFujZaKOb8PZ8PX6j
14
+ MbCTV12nEgm8FEhZQ3azxLnO17gbJ2A+Ksm/IIwnTWlqvvMZD5qTQ7L3qZuCtbWQ
15
+ +EGC/H1SDjhiwvjHcXP61/tYL/peApBSoj0L4kC+U/VaNyvicudKk08CgYEAr46J
16
+ 4VJBJfZ4ZaUBRy53+fy+mknOfaj2wo8MnD3u+/x4YWTapqvDOPN2nJVtKlIsxbS4
17
+ qCO+fzUV17YHlsQmGULNbtFuXWJkP/RcLVbe8VYg/6tmk0dJwNAe90flagX2KJov
18
+ 8eDX129nNpuUqrNNWsfeLmPmH6vUzpKlga+1zfUCgYBrbUHHJ96dmbZn2AMNtIvy
19
+ iXP3HXcj5msJwB3aKJ8eHMkU1kaWAnwxiQfrkfaQ9bCP0v6YbyQY1IJ7NlvdDs7/
20
+ dAydMtkW0WW/zyztdGN92d3vrx0QUiRTV87vt/wl7ZUXnZt1wcB5CPRCWaiUYHWx
21
+ YlDmHW6N1XdIk5DQF0OegwKBgEt7S8k3Zo9+A5IgegYy8p7njsQjy8a3qTFJ9DAR
22
+ aPmrOc8WX/SdkVihRXRZwxAZOOrgoyyYAcYL+xI+T9EBESh3UoC9R2ibb2MYG7Ha
23
+ 0gyN7a4/8eCNHCbs1QOZRAhr+8TFVqv28pbMbWJLToZ+hVns6Zikl0MyzFLtNoAm
24
+ HlMpAoGBAIOkqnwwuRKhWprL59sdcJfWY26os9nvuDV4LoKFNEFLJhj2AA2/3UlV
25
+ v85gqNSxnMNlHLZC9l2HZ3mKv/mfx1aikmFvyhJAnk5u0f9KkexmCPLjQzS5q3ba
26
+ yFuxK2DXwN4x46RgQPFlLjOTCX0BG6rkEu4JdonF8ETSjoCtGEU8
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDNjCCAh6gAwIBAgIUF9wE+oqGSbm4UVn1y9gEjzyaJFswDQYJKoZIhvcNAQEL
3
+ BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
4
+ cmF0ZWQgQ0EwHhcNMjEwODEyMDUxNTI3WhcNMjQwODExMDUxNTI3WjANMQswCQYD
5
+ VQQDEwJlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2S2by0lgyu
6
+ 1JfgGgZ41PNXbH2qMPMzowguVVdtZ16WM0CaEG7lnLxmMcC+2Q7NnGuFnPAVQo9T
7
+ Q3bh7j+1PkCJVHUKZfJIeWtGc9+qXBcO1MhedfwM1osSa4bfwM85G+XKWbRNtmSt
8
+ CoUuKArIyZkzdBAAQLBoQyPf3DIza1Au4j9Hb3zrswD6e7n2PN4ffIyil1GFduLJ
9
+ 2275qqFiOhkEDUhv7BKNftVBh/89O/5lSqAQGuQ1aDRr8TdHwhO71u4ZIU/Pn6yX
10
+ LGBWrQG53+qpdCsxGvJTfbtIEYUDTN83CirIxDKJgc1QXOEldylztHf4xnQ7ZarJ
11
+ tqF6pUzHbRsCAwEAAaNnMGUwHQYDVR0OBBYEFFQUK+6Cg2kExRj1xSDzEi4kkgKX
12
+ MB8GA1UdIwQYMBaAFMgkye5+2l+TE0I6RsXRHjGBwpBGMBgGA1UdEQQRMA+CDWVs
13
+ YXN0aWNzZWFyY2gwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAinaknZIc
14
+ 7xtQNwUwa+kdET+I4lMz+TJw9vTjGKPJqe082n81ycKU5b+a/OndG90z+dTwhShW
15
+ f0oZdIe/1rDCdiRU4ceCZA4ybKrFDIbW8gOKZOx9rsgEx9XNELj4ocZTBqxjQmNE
16
+ Ho91fli5aEm0EL2vJgejh4hcfDeElQ6go9gtvAHQ57XEADQSenvt69jOICOupnS+
17
+ LSjDVhv/VLi3CAip0B+lD5fX/DVQdrJ62eRGuQYxoouE3saCO58qUUrKB39yD9KA
18
+ qRA/sVxyLogxaU+5dLfc0NJdOqSzStxQ2vdMvAWo9tZZ2UBGFrk5SdwCQe7Yv5mX
19
+ qi02i4q6meHGcw==
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEArZLZvLSWDK7Ul+AaBnjU81dsfaow8zOjCC5VV21nXpYzQJoQ
3
+ buWcvGYxwL7ZDs2ca4Wc8BVCj1NDduHuP7U+QIlUdQpl8kh5a0Zz36pcFw7UyF51
4
+ /AzWixJrht/Azzkb5cpZtE22ZK0KhS4oCsjJmTN0EABAsGhDI9/cMjNrUC7iP0dv
5
+ fOuzAPp7ufY83h98jKKXUYV24snbbvmqoWI6GQQNSG/sEo1+1UGH/z07/mVKoBAa
6
+ 5DVoNGvxN0fCE7vW7hkhT8+frJcsYFatAbnf6ql0KzEa8lN9u0gRhQNM3zcKKsjE
7
+ MomBzVBc4SV3KXO0d/jGdDtlqsm2oXqlTMdtGwIDAQABAoIBAQCm/VBDz41ImG7p
8
+ yu3e6iMeFi7HW5SKdlRUS5dJbHT1uBWJAm/q8TbwvnUBVdsn9cKWY06QYDPQBjAy
9
+ 0LxRSIKivjyl+aIJDZbbEUXrmk/M0zT9rHtgSc2isM8ITH6IHw5q7lmNMPLYOu6T
10
+ IMvfTDtADBOOTV/vF+/4NKf5GCUXVt1XTzLBFMK0p/ZoI7Fsw7fhH6FR12vk0xA4
11
+ BEC4pwRbGfHo7P31ii0by8epkve93tF4IZuFmN92A84bN1z7Kc4TYaSbua2rgguz
12
+ FzMyWpsTxr363HzCK1xOJb6JyJOiXbq4+j2oqtne3GIvyozJeiyKRgjLIMoe/LV7
13
+ fPPc5wlhAoGBAOD3z0JH2eyR/1RHILFsWInH2nDbKHHuCjhFIL2XloeXsJkiJZ95
14
+ BpdjExMZCqD44tPNRW/GgWKwoVwltm6zB0aq0aW/OfOzw6fhKt1W+go47L7Tpwap
15
+ VQgy6BFXSueUKfQDlZEWV4E2gakf8vOl0/VRQExae/CeKf1suEedQaErAoGBAMWE
16
+ LOmNDEU2NFqghfNBAFYyFJst3YnBmSmlL7W22+OsfSK/PhxnJbuNHxMgxpg9rieW
17
+ tVyjuZRo/i7WLVm3uG+dK1RJ9t8Y6kpYkCRKpi9G8DBOj3PSulOybBr+fdRfW9mf
18
+ 8UmqOjOkrhxXPkchc9TY4EM7/1XeKvEidlIp0gvRAoGAAurz4zYvW2QhXaR2hhaT
19
+ p2XSLXiKM8AUndo3rH3U0/lhrvrEZicZsMj2LF88xg20U27sIaD/eJo13Y4XqaPk
20
+ ykPY6D9srv574SeIeMpx/8PxPiBcoDd+BNc0L1VkgVBoouORAwq5I9HjKKBjdEmI
21
+ UDw3i0X5KYvDm6fXVAZ0HXUCgYBWc4To8KiXPqNpq2sVzrSkBaWJSmj2G7u7Q6b/
22
+ RTs3is72v3gjHG6iiaE5URY7mnu4rjlRhAP9Vnsy6uHMrCJZEBTf/sPEYHZj9iGZ
23
+ EOduOAF3U1tsmaaebbDtm8hdhSOBvITy9kQlSIZAt1r17Ulytz5pj0AySFzJUIkz
24
+ a0SZkQKBgCWixtUxiK8PAdWhyS++90WJeJn8eqjuSAz+VMtFQFRRWDUbkiHvGMRu
25
+ o/Hhk6zS46gSF2Evb1d26uUEenXnJlIp6YWzb0DLPrfy5P53kPA6YEvYq5MSAg3l
26
+ DZOJUF+ko7cWXSZkeTIBH/jrGOdP4tTALZt6DNt+Gz7xwPO5tGgV
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,83 @@
1
+ # Copyright OpenSearch Contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # encoding: utf-8
5
+ require "logstash/devutils/rspec/spec_helper"
6
+ require "logstash/plugin"
7
+ require "logstash/inputs/opensearch"
8
+ require_relative "../../../spec/opensearch_helper"
9
+
10
+ describe LogStash::Inputs::OpenSearch do
11
+
12
+ let(:config) { { 'hosts' => [OpenSearchHelper.get_host_port],
13
+ 'index' => 'logs',
14
+ 'query' => '{ "query": { "match": { "message": "Not found"} }}' } }
15
+ let(:plugin) { described_class.new(config) }
16
+ let(:event) { LogStash::Event.new({}) }
17
+ let(:client_options) { Hash.new }
18
+
19
+ before(:each) do
20
+ @es = OpenSearchHelper.get_client(client_options)
21
+ # Delete all templates first.
22
+ # Clean OpenSearch of data before we start.
23
+ @es.indices.delete_template(:name => "*")
24
+ # This can fail if there are no indexes, ignore failure.
25
+ @es.indices.delete(:index => "*") rescue nil
26
+ 10.times do
27
+ OpenSearchHelper.index_doc(@es, :index => 'logs', :body => { :response => 404, :message=> 'Not Found'})
28
+ end
29
+ @es.indices.refresh
30
+ end
31
+
32
+ after(:each) do
33
+ @es.indices.delete_template(:name => "*")
34
+ @es.indices.delete(:index => "*") rescue nil
35
+ end
36
+
37
+ shared_examples 'an opensearch index plugin' do
38
+ before(:each) do
39
+ plugin.register
40
+ end
41
+
42
+ it 'should retrieve json event from opensearch' do
43
+ queue = []
44
+ plugin.run(queue)
45
+ event = queue.pop
46
+ expect(event).to be_a(LogStash::Event)
47
+ expect(event.get("response")).to eql(404)
48
+ end
49
+ end
50
+
51
+ describe 'against an unsecured opensearch', :integration => true do
52
+ before(:each) do
53
+ plugin.register
54
+ end
55
+
56
+ it_behaves_like 'an opensearch index plugin'
57
+ end
58
+
59
+ describe 'against a secured opensearch', :secure_integration => true do
60
+ let(:user) { ENV['USER'] || 'admin' }
61
+ let(:password) { ENV['PASSWORD'] || 'admin' }
62
+
63
+ let(:client_options) { { :user => user, :password => password } }
64
+
65
+ let(:config) { super().merge('user' => user, 'password' => password, 'ssl' => true) }
66
+
67
+ it_behaves_like 'an opensearch index plugin'
68
+
69
+ context "incorrect auth credentials" do
70
+
71
+ let(:config) do
72
+ super().merge('user' => 'archer', 'password' => 'b0gus!')
73
+ end
74
+
75
+ let(:queue) { [] }
76
+
77
+ it "fails to run the plugin" do
78
+ expect { plugin.register }.to raise_error OpenSearch::Transport::Transport::Errors::Unauthorized
79
+ end
80
+ end
81
+
82
+ end
83
+ end