logstash-input-elasticsearch_compat 0.1.0-java

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,295 @@
1
+ :plugin: elasticsearch_compat
2
+ :type: input
3
+ :default_codec: plain
4
+
5
+ ///////////////////////////////////////////
6
+ START - GENERATED VARIABLES, DO NOT EDIT!
7
+ ///////////////////////////////////////////
8
+ :version: %VERSION%
9
+ :release_date: %RELEASE_DATE%
10
+ :changelog_url: %CHANGELOG_URL%
11
+ :include_path: ../../../../logstash/docs/include
12
+ ///////////////////////////////////////////
13
+ END - GENERATED VARIABLES, DO NOT EDIT!
14
+ ///////////////////////////////////////////
15
+
16
+ [id="plugins-{type}s-{plugin}"]
17
+
18
+ === Elasticsearch compat input plugin
19
+
20
+ include::{include_path}/plugin_header.asciidoc[]
21
+
22
+ ==== Description
23
+
24
+ Receives Elasticsearch Bulk API requests and emits one Logstash event per `index` or `create`
25
+ action. Point a client that normally writes to Elasticsearch, such as the OpenTelemetry
26
+ Collector `elasticsearch` exporter (tested with its default settings), at Logstash instead.
27
+
28
+ This plugin is a protocol-compatible receiver, not an Elasticsearch node.
29
+
30
+ ===== Routes
31
+
32
+ * `POST` or `PUT` `/_bulk` and `/{index}/_bulk`. On `/{index}/_bulk`, `{index}` is used only for
33
+ actions without `_index`.
34
+ * `GET /` and `HEAD /` return a minimal identity response for client version detection. It
35
+ reports version `8.19.0` as a fixed compatibility placeholder (it keeps the OTel exporter on its
36
+ most widely compatible metrics format).
37
+ * Anything else: `404`; wrong methods: `405`.
38
+
39
+ ===== Events
40
+
41
+ Event fields are the source document. Routing metadata (`action`, `index`, and `id`, `routing`,
42
+ `pipeline` when supplied) is stored at `metadata_target`
43
+ (default `[@metadata][elasticsearch_compat]`). `@metadata` is not written by outputs; copy what
44
+ downstream consumers need.
45
+
46
+ A numeric or digit-string `@timestamp` is read as epoch milliseconds, as Elasticsearch reads it
47
+ (the OTel exporter sends this form), with nanosecond precision.
48
+
49
+ ===== Acknowledgments
50
+
51
+ * An item's `201` means the event was accepted by the Logstash queue, not that an output
52
+ delivered it.
53
+ * `update` and `delete` are not supported: each gets an item-level `400`.
54
+ * A malformed body gets `400` and nothing from it is queued.
55
+ * If queuing fails mid-request: items already queued are `201`, the failing item `500`, the rest
56
+ `503`. Clients retry these, so duplicates are possible.
57
+
58
+ ===== Compression
59
+
60
+ Compressed request bodies (`gzip`, `deflate`, `x-gzip`, `x-deflate`) are always accepted;
61
+ `max_content_length` applies after decompression. Other encodings get `415`. Responses are
62
+ compressed (level 3, no size threshold) when the client accepts it, unless
63
+ `compression_enabled => false`.
64
+
65
+ ===== Credentials
66
+
67
+ `Authorization` headers are accepted and ignored; no request ever gets `401` or `403`. Restrict
68
+ access with TLS client certificates (`ssl_client_authentication`) or at the network layer.
69
+
70
+ ===== Monitoring
71
+
72
+ This input adds counters to its entry in the Logstash node stats API
73
+ (`GET /_node/stats/pipelines/<id>`): `bulk_requests`, `bulk_requests_rejected_overload` (`429`),
74
+ `bulk_requests_rejected_too_large` (`413`), `bulk_requests_rejected_unsupported_encoding`
75
+ (`415`), `bulk_requests_rejected_bad_request` (`400`), `bulk_items_failed`, and
76
+ `queue_submit_failures`. They are updated per request and visible within about a second. Disable
77
+ with `enable_metric => false`.
78
+
79
+ [id="plugins-{type}s-{plugin}-options"]
80
+ ==== Elasticsearch compat input configuration options
81
+
82
+ [cols="<,<,<",options="header",]
83
+ |=======================================================================
84
+ |Setting |Input type|Required
85
+ | <<plugins-{type}s-{plugin}-compression_enabled>> |<<boolean,boolean>>|No
86
+ | <<plugins-{type}s-{plugin}-host>> |<<string,string>>|No
87
+ | <<plugins-{type}s-{plugin}-max_content_length>> |<<number,number>>|No
88
+ | <<plugins-{type}s-{plugin}-max_pending_requests>> |<<number,number>>|No
89
+ | <<plugins-{type}s-{plugin}-metadata_target>> |<<string,string>>|No
90
+ | <<plugins-{type}s-{plugin}-port>> |<<number,number>>|No
91
+ | <<plugins-{type}s-{plugin}-ssl_certificate>> |a valid filesystem path|No
92
+ | <<plugins-{type}s-{plugin}-ssl_certificate_authorities>> |<<array,array>>|No
93
+ | <<plugins-{type}s-{plugin}-ssl_cipher_suites>> |<<array,array>>|No
94
+ | <<plugins-{type}s-{plugin}-ssl_client_authentication>> |<<string,string>>, one of `["none", "optional", "required"]`|No
95
+ | <<plugins-{type}s-{plugin}-ssl_enabled>> |<<boolean,boolean>>|No
96
+ | <<plugins-{type}s-{plugin}-ssl_handshake_timeout>> |<<number,number>>|No
97
+ | <<plugins-{type}s-{plugin}-ssl_key>> |a valid filesystem path|No
98
+ | <<plugins-{type}s-{plugin}-ssl_key_passphrase>> |<<password,password>>|No
99
+ | <<plugins-{type}s-{plugin}-ssl_keystore_password>> |<<password,password>>|No
100
+ | <<plugins-{type}s-{plugin}-ssl_keystore_path>> |a valid filesystem path|No
101
+ | <<plugins-{type}s-{plugin}-ssl_keystore_type>> |<<string,string>>, one of `["pkcs12", "jks"]`|No
102
+ | <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
103
+ | <<plugins-{type}s-{plugin}-ssl_truststore_password>> |<<password,password>>|No
104
+ | <<plugins-{type}s-{plugin}-ssl_truststore_path>> |a valid filesystem path|No
105
+ | <<plugins-{type}s-{plugin}-threads>> |<<number,number>>|No
106
+ |=======================================================================
107
+
108
+ Also see <<plugins-{type}s-{plugin}-common-options>> for a list of options supported by all
109
+ input plugins.
110
+
111
+ &nbsp;
112
+
113
+ [id="plugins-{type}s-{plugin}-compression_enabled"]
114
+ ===== `compression_enabled`
115
+
116
+ * Value type is <<boolean,boolean>>
117
+ * Default value is `true`
118
+
119
+ Compress responses when the client accepts gzip or deflate. Compressed requests are always
120
+ accepted regardless of this setting.
121
+
122
+ [id="plugins-{type}s-{plugin}-host"]
123
+ ===== `host`
124
+
125
+ * Value type is <<string,string>>
126
+ * Default value is `"0.0.0.0"`
127
+
128
+ The address to listen on.
129
+
130
+ [id="plugins-{type}s-{plugin}-max_content_length"]
131
+ ===== `max_content_length`
132
+
133
+ * Value type is <<number,number>>
134
+ * Default value is `104857600` (100 MB)
135
+
136
+ Maximum request body size in bytes, after decompression. Larger requests get `413`.
137
+
138
+ [id="plugins-{type}s-{plugin}-max_pending_requests"]
139
+ ===== `max_pending_requests`
140
+
141
+ * Value type is <<number,number>>
142
+ * Default value is `200`
143
+
144
+ Requests waiting for a worker thread. Beyond this, new requests get `429` (retryable) before
145
+ anything is queued.
146
+
147
+ [id="plugins-{type}s-{plugin}-metadata_target"]
148
+ ===== `metadata_target`
149
+
150
+ * Value type is <<string,string>>
151
+ * Default value is `"[@metadata][elasticsearch_compat]"`
152
+
153
+ Field where routing metadata is stored.
154
+
155
+ [id="plugins-{type}s-{plugin}-port"]
156
+ ===== `port`
157
+
158
+ * Value type is <<number,number>>
159
+ * Default value is `9200`
160
+
161
+ The port to listen on.
162
+
163
+ [id="plugins-{type}s-{plugin}-ssl_certificate"]
164
+ ===== `ssl_certificate`
165
+
166
+ * Value type is <<path,path>>
167
+ * There is no default value for this setting.
168
+
169
+ PEM certificate (chain). Requires `ssl_key`. Use either this or `ssl_keystore_path`.
170
+
171
+ [id="plugins-{type}s-{plugin}-ssl_certificate_authorities"]
172
+ ===== `ssl_certificate_authorities`
173
+
174
+ * Value type is <<array,array>>
175
+ * Default value is `[]`
176
+
177
+ PEM certificate authorities that client certificates are checked against. Requires
178
+ `ssl_client_authentication` `optional` or `required`. Use either this or `ssl_truststore_path`.
179
+
180
+ [id="plugins-{type}s-{plugin}-ssl_cipher_suites"]
181
+ ===== `ssl_cipher_suites`
182
+
183
+ * Value type is a list of <<string,string>>
184
+ * Default value is a modern list (TLS 1.3 suites and ECDHE AES-GCM/ChaCha20, then ECDHE AES-CBC),
185
+ limited to those the JVM supports — the same as `logstash-input-http`.
186
+
187
+ Cipher suites to enable, in order of preference.
188
+
189
+ [id="plugins-{type}s-{plugin}-ssl_client_authentication"]
190
+ ===== `ssl_client_authentication`
191
+
192
+ * Value can be any of: `none`, `optional`, `required`
193
+ * Default value is `"none"`
194
+
195
+ Whether to request (`optional`) or require (`required`) a client certificate. Needs
196
+ `ssl_certificate_authorities`, `ssl_truststore_path`, or a keystore that also holds trusted
197
+ certificates.
198
+
199
+ [id="plugins-{type}s-{plugin}-ssl_enabled"]
200
+ ===== `ssl_enabled`
201
+
202
+ * Value type is <<boolean,boolean>>
203
+ * Default value is `false`
204
+
205
+ Serve HTTPS. Requires `ssl_certificate` and `ssl_key`, or `ssl_keystore_path`. Other `ssl_*`
206
+ settings with `ssl_enabled => false` are ignored with a warning.
207
+
208
+ [id="plugins-{type}s-{plugin}-ssl_handshake_timeout"]
209
+ ===== `ssl_handshake_timeout`
210
+
211
+ * Value type is <<number,number>>
212
+ * Default value is `10000`
213
+
214
+ Time in milliseconds for an incomplete TLS handshake before the connection is closed.
215
+
216
+ [id="plugins-{type}s-{plugin}-ssl_key"]
217
+ ===== `ssl_key`
218
+
219
+ * Value type is <<path,path>>
220
+ * There is no default value for this setting.
221
+
222
+ PEM private key in PKCS#8 format (convert PKCS#1 with
223
+ `openssl pkcs8 -topk8 -nocrypt -in key.pem -out key.pkcs8.pem`). Requires `ssl_certificate`.
224
+
225
+ [id="plugins-{type}s-{plugin}-ssl_key_passphrase"]
226
+ ===== `ssl_key_passphrase`
227
+
228
+ * Value type is <<password,password>>
229
+ * There is no default value for this setting.
230
+
231
+ Passphrase of an encrypted `ssl_key`.
232
+
233
+ [id="plugins-{type}s-{plugin}-ssl_keystore_password"]
234
+ ===== `ssl_keystore_password`
235
+
236
+ * Value type is <<password,password>>
237
+ * There is no default value for this setting.
238
+
239
+ Keystore password.
240
+
241
+ [id="plugins-{type}s-{plugin}-ssl_keystore_path"]
242
+ ===== `ssl_keystore_path`
243
+
244
+ * Value type is <<path,path>>
245
+ * There is no default value for this setting.
246
+
247
+ PKCS12 or JKS keystore with the server key and certificate. Use either this or `ssl_certificate`.
248
+
249
+ [id="plugins-{type}s-{plugin}-ssl_keystore_type"]
250
+ ===== `ssl_keystore_type`
251
+
252
+ * Value can be any of: `pkcs12`, `jks`
253
+ * There is no default value; when unset, `.p12`/`.pfx`/`.pkcs12` files are PKCS12 and others JKS.
254
+
255
+ Keystore format.
256
+
257
+ [id="plugins-{type}s-{plugin}-ssl_supported_protocols"]
258
+ ===== `ssl_supported_protocols`
259
+
260
+ * Value type is <<string,string>>
261
+ * Allowed values are: `'TLSv1.1'`, `'TLSv1.2'`, `'TLSv1.3'`
262
+ * Default value is `['TLSv1.2', 'TLSv1.3']`
263
+
264
+ TLS versions to accept. The JVM disables `TLSv1.1` by default.
265
+
266
+ [id="plugins-{type}s-{plugin}-ssl_truststore_password"]
267
+ ===== `ssl_truststore_password`
268
+
269
+ * Value type is <<password,password>>
270
+ * There is no default value for this setting.
271
+
272
+ Truststore password.
273
+
274
+ [id="plugins-{type}s-{plugin}-ssl_truststore_path"]
275
+ ===== `ssl_truststore_path`
276
+
277
+ * Value type is <<path,path>>
278
+ * There is no default value for this setting.
279
+
280
+ PKCS12 or JKS truststore with the certificates client certificates are checked against. Requires
281
+ `ssl_client_authentication` `optional` or `required`. Use either this or
282
+ `ssl_certificate_authorities`.
283
+
284
+ [id="plugins-{type}s-{plugin}-threads"]
285
+ ===== `threads`
286
+
287
+ * Value type is <<number,number>>
288
+ * Default value is the number of CPU cores
289
+
290
+ Worker threads that parse requests and queue events.
291
+
292
+ [id="plugins-{type}s-{plugin}-common-options"]
293
+ include::{include_path}/{type}.asciidoc[]
294
+
295
+ :default_codec!:
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ require "java"
3
+
4
+ module LogStash
5
+ module ElasticsearchCompat
6
+ # Implements the Java BatchHandler: called once per request from a worker thread.
7
+ # Pushes one event per operation, in order, and records each known outcome.
8
+ # Entries left nil are reported by Java as NOT_ATTEMPTED (503, safe to retry).
9
+ class BatchHandler
10
+ include Java::OrgLogstashPluginsInputsEscompat::BatchHandler
11
+
12
+ ItemOutcome = Java::OrgLogstashPluginsInputsEscompatBulk::ItemOutcome
13
+ QUEUED = ItemOutcome::QUEUED
14
+ SUBMIT_FAILED = ItemOutcome::SUBMIT_FAILED
15
+
16
+ # @param sink [#call] called with (document, metadata); builds and queues one event.
17
+ # Raising means the outcome is unknown.
18
+ # @param logger [#error]
19
+ # @param after_submit [#call, nil] called once after each request's submission (metrics)
20
+ def initialize(sink, logger, after_submit = nil)
21
+ @sink = sink
22
+ @logger = logger
23
+ @after_submit = after_submit
24
+ @stopping = java.util.concurrent.atomic.AtomicBoolean.new(false)
25
+ end
26
+
27
+ # After this, operations not yet started are left NOT_ATTEMPTED.
28
+ def stop!
29
+ @stopping.set(true)
30
+ end
31
+
32
+ def submit(ops, results)
33
+ submit_all(ops, results)
34
+ ensure
35
+ begin
36
+ @after_submit.call if @after_submit
37
+ rescue StandardError, java.lang.Exception
38
+ # metrics must never affect ingestion
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def submit_all(ops, results)
45
+ n = ops.size
46
+ i = 0
47
+ while i < n
48
+ return if @stopping.get
49
+ op = ops.get(i)
50
+ begin
51
+ @sink.call(op.document, op.metadata)
52
+ rescue StandardError, java.lang.Exception => e
53
+ results[i] = SUBMIT_FAILED
54
+ # Never log the document (constitution VI).
55
+ @logger.error("elasticsearch_compat: failed to queue event; remaining items in the " \
56
+ "request were not attempted", :exception => e.class.name, :message => e.message)
57
+ return
58
+ end
59
+ results[i] = QUEUED
60
+ i += 1
61
+ end
62
+ nil
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ module LogStash
4
+ module ElasticsearchCompat
5
+ # Publishes the Java RequestCounters into this input's Logstash metrics as gauges of
6
+ # cumulative values (research R6): after each request that reaches Ruby, and every second so
7
+ # refused requests (which never reach Ruby) still show up. Gauges are idempotent, so
8
+ # concurrent publishes need no locking. With enable_metric => false, `metric` is Logstash's
9
+ # null metric and nothing is reported.
10
+ class MetricsPublisher
11
+ INTERVAL_SECONDS = 1
12
+
13
+ # @param metric [#gauge] the plugin's namespaced metric
14
+ # @param counters [#snapshot] Java RequestCounters
15
+ def initialize(metric, counters)
16
+ @metric = metric
17
+ @counters = counters
18
+ @thread = nil
19
+ @running = java.util.concurrent.atomic.AtomicBoolean.new(false)
20
+ end
21
+
22
+ def publish
23
+ @counters.snapshot.each { |key, value| @metric.gauge(key.to_sym, value) }
24
+ end
25
+
26
+ def start
27
+ return unless @running.compare_and_set(false, true)
28
+ @thread = Thread.new do
29
+ Thread.current.name = "escompat-metrics" if Thread.current.respond_to?(:name=)
30
+ while @running.get
31
+ begin
32
+ publish
33
+ rescue StandardError, java.lang.Exception
34
+ # metrics must never affect ingestion
35
+ end
36
+ sleep INTERVAL_SECONDS
37
+ end
38
+ end
39
+ end
40
+
41
+ def stop
42
+ @running.set(false)
43
+ thread = @thread
44
+ thread.wakeup rescue nil if thread
45
+ thread.join(5) if thread
46
+ publish rescue nil
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ module LogStash
4
+ module ElasticsearchCompat
5
+ # TLS setting rules identical to logstash-input-http's validate_ssl_settings!, including its
6
+ # messages (contracts/plugin-config.md rules 1-6). Pure: no Logstash dependency, so it can be
7
+ # specified with plain JRuby.
8
+ module SslValidation
9
+ OPTIONAL = "optional".freeze
10
+ REQUIRED = "required".freeze
11
+
12
+ # @param s [Hash] setting name (String) => value, as configured
13
+ # @param explicit [Array<String>] names the user actually set (for the warning)
14
+ # @return [Hash] { errors: [String] (first rule only, as logstash-input-http raises), warnings: [String] }
15
+ def self.check(s, explicit)
16
+ unless s["ssl_enabled"]
17
+ ignored = explicit.select { |k| k != "ssl_enabled" && k.start_with?("ssl_") }
18
+ warnings = ignored.empty? ? [] :
19
+ ["Configured SSL settings are not used when `ssl_enabled` is set to `false`: #{ignored}"]
20
+ return { errors: [], warnings: warnings }
21
+ end
22
+ { errors: Array(first_error(s)), warnings: [] }
23
+ end
24
+
25
+ def self.first_error(s)
26
+ cert = present?(s["ssl_certificate"])
27
+ key = present?(s["ssl_key"])
28
+ keystore = present?(s["ssl_keystore_path"])
29
+ truststore = present?(s["ssl_truststore_path"])
30
+ cas = Array(s["ssl_certificate_authorities"]).any?
31
+ client_auth = s["ssl_client_authentication"].to_s.downcase
32
+
33
+ return "Using an `ssl_certificate` requires an `ssl_key`" if cert && !key
34
+ return "An `ssl_certificate` is required when using an `ssl_key`" if key && !cert
35
+ unless cert || keystore
36
+ return "Either an `ssl_certificate` or `ssl_keystore_path` is required when SSL is enabled `ssl_enabled => true`"
37
+ end
38
+ return "Use either an `ssl_certificate` or an `ssl_keystore_path`" if cert && keystore
39
+ return "Use either an `ssl_certificate_authorities` or an `ssl_truststore_path`" if cas && truststore
40
+
41
+ if client_auth == OPTIONAL || client_auth == REQUIRED
42
+ unless cas || truststore || keystore
43
+ return "Using `ssl_client_authentication` set to `#{OPTIONAL}` or `#{REQUIRED}`, requires the " \
44
+ "configuration of `ssl_certificate_authorities` or `ssl_truststore_path`"
45
+ end
46
+ else
47
+ if truststore
48
+ return "The configuration of `ssl_truststore_path` requires setting `ssl_client_authentication` " \
49
+ "to `#{OPTIONAL}` or '#{REQUIRED}'"
50
+ end
51
+ if cas
52
+ return "The configuration of `ssl_certificate_authorities` requires setting " \
53
+ "`ssl_client_authentication` to `#{OPTIONAL}` or '#{REQUIRED}'"
54
+ end
55
+ end
56
+ nil
57
+ end
58
+
59
+ def self.present?(v)
60
+ !(v.nil? || v.to_s.empty?)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/base"
3
+ require "logstash/namespace"
4
+ require "logstash-input-elasticsearch_compat_jars"
5
+ require "logstash/inputs/elasticsearch_compat/batch_handler"
6
+ require "logstash/inputs/elasticsearch_compat/ssl_validation"
7
+ require "logstash/inputs/elasticsearch_compat/metrics_publisher"
8
+
9
+ # Receives Elasticsearch Bulk API requests (POST /_bulk) and emits one event per
10
+ # index/create action. Items are acknowledged only after the event is accepted by
11
+ # the Logstash queue. update/delete get item-level errors. Credentials are accepted
12
+ # and ignored.
13
+ class LogStash::Inputs::ElasticsearchCompat < LogStash::Inputs::Base
14
+ config_name "elasticsearch_compat"
15
+
16
+ # Bulk bodies are parsed by the Java backend, not by a codec.
17
+ default :codec, "plain"
18
+
19
+ # Listen address.
20
+ config :host, :validate => :string, :default => "0.0.0.0"
21
+
22
+ # Listen port.
23
+ config :port, :validate => :number, :default => 9200
24
+
25
+ # Worker threads that parse requests and push events.
26
+ config :threads, :validate => :number, :default => ::LogStash::Config::CpuCoreStrategy.maximum
27
+
28
+ # Requests waiting for a worker; beyond this, new requests get 429.
29
+ config :max_pending_requests, :validate => :number, :default => 200
30
+
31
+ # Maximum request body size in bytes; larger bodies get 413.
32
+ config :max_content_length, :validate => :number, :default => 100 * 1024 * 1024
33
+
34
+ # Where routing metadata (action, index, id, routing, pipeline) is stored.
35
+ config :metadata_target, :validate => :string, :default => "[@metadata][elasticsearch_compat]"
36
+
37
+ # Compress responses when the client accepts gzip or deflate (level 3, like Elasticsearch's
38
+ # http.compression). Compressed request bodies are always accepted.
39
+ config :compression_enabled, :validate => :boolean, :default => true
40
+
41
+ # TLS: same settings, defaults and rules as logstash-input-http.
42
+ config :ssl_enabled, :validate => :boolean, :default => false
43
+ config :ssl_certificate, :validate => :path
44
+ config :ssl_key, :validate => :path
45
+ config :ssl_key_passphrase, :validate => :password
46
+ config :ssl_keystore_path, :validate => :path
47
+ config :ssl_keystore_password, :validate => :password
48
+ config :ssl_keystore_type, :validate => %w[pkcs12 jks]
49
+ config :ssl_certificate_authorities, :validate => :array, :default => []
50
+ config :ssl_truststore_path, :validate => :path
51
+ config :ssl_truststore_password, :validate => :password
52
+ config :ssl_client_authentication, :validate => %w[none optional required], :default => "none"
53
+ config :ssl_supported_protocols, :validate => %w[TLSv1.1 TLSv1.2 TLSv1.3], :default => %w[TLSv1.2 TLSv1.3],
54
+ :list => true
55
+ config :ssl_cipher_suites,
56
+ :validate => Java::OrgLogstashPluginsInputsEscompatServerTls::SslContextFactory.supportedCiphers.to_a,
57
+ :default => Java::OrgLogstashPluginsInputsEscompatServerTls::SslContextFactory.defaultCiphers.to_a,
58
+ :list => true
59
+ config :ssl_handshake_timeout, :validate => :number, :default => 10000
60
+
61
+ def register
62
+ [[:threads, @threads], [:max_pending_requests, @max_pending_requests],
63
+ [:max_content_length, @max_content_length]].each do |name, value|
64
+ raise LogStash::ConfigurationError, "#{name} must be > 0" unless value > 0
65
+ end
66
+ if @max_content_length > java.lang.Integer::MAX_VALUE
67
+ raise LogStash::ConfigurationError, "max_content_length must be <= #{java.lang.Integer::MAX_VALUE}"
68
+ end
69
+
70
+ raise LogStash::ConfigurationError, "ssl_handshake_timeout must be > 0" unless @ssl_handshake_timeout > 0
71
+
72
+ counters = Java::OrgLogstashPluginsInputsEscompatServer::RequestCounters.new
73
+ @metrics_publisher = LogStash::ElasticsearchCompat::MetricsPublisher.new(metric, counters)
74
+ @batch_handler = LogStash::ElasticsearchCompat::BatchHandler.new(
75
+ method(:push_event), logger, @metrics_publisher.method(:publish))
76
+
77
+ config = Java::OrgLogstashPluginsInputsEscompat::ServerConfig.builder
78
+ .name("escompat-#{id}").host(@host).port(@port).threads(@threads)
79
+ .maxPendingRequests(@max_pending_requests).maxContentLength(@max_content_length)
80
+ .compressionEnabled(@compression_enabled).counters(counters)
81
+ .sslContext(build_ssl_context).sslHandshakeTimeoutMillis(@ssl_handshake_timeout)
82
+ .build
83
+ @server = Java::OrgLogstashPluginsInputsEscompatServer::EsCompatServer.new(config, @batch_handler)
84
+ end
85
+
86
+ def run(queue)
87
+ @queue = queue
88
+ logger.info("Starting elasticsearch_compat input", :address => "#{@host}:#{@port}", :ssl_enabled => @ssl_enabled)
89
+ @metrics_publisher.start
90
+ @server.run
91
+ end
92
+
93
+ def stop
94
+ @batch_handler.stop! if @batch_handler
95
+ @server.close if @server
96
+ @metrics_publisher.stop if @metrics_publisher
97
+ rescue => e
98
+ logger.debug("error while stopping elasticsearch_compat input", :exception => e.class.name)
99
+ end
100
+
101
+ def close
102
+ @server.close if @server
103
+ @metrics_publisher.stop if @metrics_publisher
104
+ rescue => e
105
+ logger.debug("error while closing elasticsearch_compat input", :exception => e.class.name)
106
+ end
107
+
108
+ private
109
+
110
+ # Validates like logstash-input-http, then builds the SslContext (nil when TLS is off).
111
+ def build_ssl_context
112
+ settings = {
113
+ "ssl_enabled" => @ssl_enabled, "ssl_certificate" => @ssl_certificate, "ssl_key" => @ssl_key,
114
+ "ssl_keystore_path" => @ssl_keystore_path, "ssl_truststore_path" => @ssl_truststore_path,
115
+ "ssl_certificate_authorities" => @ssl_certificate_authorities,
116
+ "ssl_client_authentication" => @ssl_client_authentication
117
+ }
118
+ explicit = original_params.keys.select { |k| k.start_with?("ssl_") }
119
+ result = LogStash::ElasticsearchCompat::SslValidation.check(settings, explicit)
120
+ result[:warnings].each { |w| logger.warn(w) }
121
+ raise LogStash::ConfigurationError, result[:errors].first if result[:errors].any?
122
+ return nil unless @ssl_enabled
123
+
124
+ tls = Java::OrgLogstashPluginsInputsEscompatServerTls::TlsSettings.new(
125
+ @ssl_certificate, @ssl_key, secret(@ssl_key_passphrase),
126
+ @ssl_keystore_path, secret(@ssl_keystore_password), @ssl_keystore_type,
127
+ @ssl_certificate_authorities, @ssl_truststore_path, secret(@ssl_truststore_password),
128
+ @ssl_client_authentication, @ssl_supported_protocols, @ssl_cipher_suites)
129
+ Java::OrgLogstashPluginsInputsEscompatServerTls::SslContextFactory.build(tls)
130
+ rescue java.lang.IllegalArgumentException => e
131
+ logger.error("SSL configuration invalid", :message => e.message)
132
+ raise LogStash::ConfigurationError, e.message
133
+ rescue java.lang.Exception => e
134
+ logger.error("SSL configuration failed", :exception => e.class.name, :message => e.message)
135
+ raise LogStash::ConfigurationError, e.message
136
+ end
137
+
138
+ def secret(password)
139
+ password.nil? ? nil : password.value
140
+ end
141
+
142
+ # Called from Java worker threads via the batch handler.
143
+ def push_event(document, metadata)
144
+ event = LogStash::Event.new(document)
145
+ event.set(@metadata_target, metadata)
146
+ decorate(event)
147
+ @queue << event
148
+ end
149
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ require "jar_dependencies"
3
+
4
+ require_jar("org.logstash.plugins.inputs", "logstash-input-elasticsearch_compat",
5
+ File.read(File.expand_path("../VERSION", __dir__)).strip)
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "logstash-input-elasticsearch_compat"
3
+ s.version = File.read(File.expand_path("VERSION", __dir__)).strip
4
+ s.licenses = ["Apache-2.0"]
5
+ s.summary = "Receives Elasticsearch Bulk API requests and emits Logstash events"
6
+ s.description = "This gem is a Logstash plugin required to be installed on top of the " \
7
+ "Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. " \
8
+ "This gem is not a stand-alone program."
9
+ s.authors = ["J. M. Becker"]
10
+ s.homepage = "https://github.com/techzilla/logstash-input-elasticsearch_compat"
11
+ s.require_paths = ["lib", "vendor/jar-dependencies"]
12
+ s.platform = "java"
13
+ s.required_ruby_version = ">= 3.1" # JRuby 9.4+, as bundled across Logstash 9.x
14
+
15
+ s.files = Dir["lib/**/*", "vendor/jar-dependencies/**/*.jar", "docs/**/*", "*.gemspec", "*.md",
16
+ "VERSION", "LICENSE", "NOTICE.TXT"]
17
+
18
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
19
+
20
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
21
+ s.add_runtime_dependency "logstash-codec-plain", "~> 3.1"
22
+
23
+ s.add_development_dependency "rspec", "~> 3.13"
24
+ end