logstash-output-tcp 6.1.2 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd2acefd6c26058e3e2afc94abb73de74e2816ca57a6379229909434ad4c72e9
4
- data.tar.gz: 38633ca4cf74d5360fa3644b9624cb21db997e9cda5ff8846f5ca510b811c133
3
+ metadata.gz: 9b97e9b9113d4cb10180102400b3a6ca6188f0a835df2628c059867ea0615fcf
4
+ data.tar.gz: b13b7dc96ea37efdf72e697396ebcadfeb864c09a65465182dc9a9c3a7c78eec
5
5
  SHA512:
6
- metadata.gz: ef785946ab821ca9347ef5fd04e17909ef573155ce790955640b875582a268363c436c3fa6a6c8c8cc19d086a6a9e6d5b8452448be59fbd80328481a7e0f292e
7
- data.tar.gz: ac1c3b27079ecb0bb91eaa4a11eea17a1f79d4b6d935e72d82085b34f6731ee2ed6e6240250e4c18429b669d1430875d8ef958aa4c009ae71bb75d3e73f6a414
6
+ metadata.gz: '08058e53dfcd3f6fcbf4c1e51a82e59998f6ad4348b9dc16c57a86393a92353a7e9c2396984183b2577afe2c35162a333b4d0c5c5e5aac855ffafa3a5a1c0836'
7
+ data.tar.gz: f97cf1b1e6ddb5c5aeb01fe9f5cbb68aa71bf3798424a219095a8fe3a6b1678c3c7480f059769948e736277daf31f719eecd29a69b4a43bf81195ffa03025462
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 6.2.0
2
+ - Reviewed and deprecated SSL settings to comply with Logstash's naming convention [#53](https://github.com/logstash-plugins/logstash-output-tcp/pull/53)
3
+ - Deprecated `ssl_enable` in favor of `ssl_enabled`
4
+ - Deprecated `ssl_cert` in favor of `ssl_certificate`
5
+ - Deprecated `ssl_verify` in favor of `ssl_client_authentication` when mode is `server`
6
+ - Deprecated `ssl_verify` in favor of `ssl_verification_mode` when mode is `client`
7
+ - Added `ssl_cipher_suites` configuration
8
+ - Added SSL configuration validations
9
+
1
10
  ## 6.1.2
2
11
  - Changed the client mode to write using the non-blocking method. [#52](https://github.com/logstash-plugins/logstash-output-tcp/pull/52)
3
12
 
data/docs/index.asciidoc CHANGED
@@ -40,12 +40,18 @@ This plugin supports the following configuration options plus the <<plugins-{typ
40
40
  | <<plugins-{type}s-{plugin}-mode>> |<<string,string>>, one of `["server", "client"]`|No
41
41
  | <<plugins-{type}s-{plugin}-port>> |<<number,number>>|Yes
42
42
  | <<plugins-{type}s-{plugin}-reconnect_interval>> |<<number,number>>|No
43
- | <<plugins-{type}s-{plugin}-ssl_cacert>> |a valid filesystem path|No
44
- | <<plugins-{type}s-{plugin}-ssl_cert>> |a valid filesystem path|No
45
- | <<plugins-{type}s-{plugin}-ssl_enable>> |<<boolean,boolean>>|No
43
+ | <<plugins-{type}s-{plugin}-ssl_cacert>> |a valid filesystem path|__Deprecated__
44
+ | <<plugins-{type}s-{plugin}-ssl_cert>> |a valid filesystem path|__Deprecated__
45
+ | <<plugins-{type}s-{plugin}-ssl_certificate>> |a valid filesystem path|No
46
+ | <<plugins-{type}s-{plugin}-ssl_certificate_authorities>> |<<array,array>>|No
47
+ | <<plugins-{type}s-{plugin}-ssl_cipher_suites>> |<<string,string>>|No
48
+ | <<plugins-{type}s-{plugin}-ssl_client_authentication>> |<<string,string>>, one of `["none", "optional", "required"]`|No
49
+ | <<plugins-{type}s-{plugin}-ssl_enable>> |<<boolean,boolean>>|__Deprecated__
50
+ | <<plugins-{type}s-{plugin}-ssl_enabled>> |<<boolean,boolean>>|No
46
51
  | <<plugins-{type}s-{plugin}-ssl_key>> |a valid filesystem path|No
47
52
  | <<plugins-{type}s-{plugin}-ssl_key_passphrase>> |<<password,password>>|No
48
53
  | <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
54
+ | <<plugins-{type}s-{plugin}-ssl_verification_mode>> |<<string,string>>, one of `["full", "none"]`|No
49
55
  | <<plugins-{type}s-{plugin}-ssl_verify>> |<<boolean,boolean>>|No
50
56
  |=======================================================================
51
57
 
@@ -93,6 +99,7 @@ When connect failed,retry interval in sec.
93
99
 
94
100
  [id="plugins-{type}s-{plugin}-ssl_cacert"]
95
101
  ===== `ssl_cacert`
102
+ deprecated[6.2.0, Replaced by <<plugins-{type}s-{plugin}-ssl_certificate_authorities>>]
96
103
 
97
104
  * Value type is <<path,path>>
98
105
  * There is no default value for this setting.
@@ -101,14 +108,69 @@ The SSL CA certificate, chainfile or CA path. The system CA path is automaticall
101
108
 
102
109
  [id="plugins-{type}s-{plugin}-ssl_cert"]
103
110
  ===== `ssl_cert`
111
+ deprecated[6.2.0, Replaced by <<plugins-{type}s-{plugin}-ssl_certificate>>]
104
112
 
105
113
  * Value type is <<path,path>>
106
114
  * There is no default value for this setting.
107
115
 
108
116
  SSL certificate path
109
117
 
118
+ [id="plugins-{type}s-{plugin}-ssl_certificate"]
119
+ ===== `ssl_certificate`
120
+
121
+ * Value type is <<path,path>>
122
+ * There is no default value for this setting.
123
+
124
+ Path to certificate in PEM format. This certificate will be presented
125
+ to the other part of the TLS connection.
126
+
127
+ [id="plugins-{type}s-{plugin}-ssl_certificate_authorities"]
128
+ ===== `ssl_certificate_authorities`
129
+
130
+ * Value type is <<array,array>>
131
+ * Default value is `[]`
132
+
133
+ Validate client certificate or certificate chain against these authorities.
134
+ You can define multiple files. All the certificates will be read and added to the trust store.
135
+ The system CA path is automatically included.
136
+
137
+ [id="plugins-{type}s-{plugin}-ssl_cipher_suites"]
138
+ ===== `ssl_cipher_suites`
139
+
140
+ * Value type is a list of <<string,string>>
141
+ * There is no default value for this setting
142
+
143
+ The list of cipher suites to use, listed by priorities.
144
+ Supported cipher suites vary depending on the Java and protocol versions.
145
+
146
+ [id="plugins-{type}s-{plugin}-ssl_client_authentication"]
147
+ ===== `ssl_client_authentication`
148
+
149
+ * Value can be any of: `none`, `optional`, `required`
150
+ * Default value is `none`
151
+
152
+ Controls the server's behavior in regard to requesting a certificate from client connections:
153
+ `none` disables the client authentication. `required` forces a client to present a certificate, while `optional` requests a client certificate
154
+ but the client is not required to present one.
155
+
156
+ When mutual TLS is enabled (`optional` or `required`), the certificate presented by the client must be signed by trusted
157
+ <<plugins-{type}s-{plugin}-ssl_certificate_authorities>> (CAs).
158
+ Please note that the server does not validate the client certificate CN (Common Name) or SAN (Subject Alternative Name).
159
+
160
+ NOTE: This setting can be used only if <<plugins-{type}s-{plugin}-mode>> is `server` and <<plugins-{type}s-{plugin}-ssl_certificate_authorities>> is set.
161
+
162
+
110
163
  [id="plugins-{type}s-{plugin}-ssl_enable"]
111
164
  ===== `ssl_enable`
165
+ deprecated[6.2.0, Replaced by <<plugins-{type}s-{plugin}-ssl_enabled>>]
166
+
167
+ * Value type is <<boolean,boolean>>
168
+ * Default value is `false`
169
+
170
+ Enable SSL (must be set for other `ssl_` options to take effect).
171
+
172
+ [id="plugins-{type}s-{plugin}-ssl_enabled"]
173
+ ===== `ssl_enabled`
112
174
 
113
175
  * Value type is <<boolean,boolean>>
114
176
  * Default value is `false`
@@ -145,8 +207,25 @@ NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as
145
207
  the protocol is disabled by default and needs to be enabled manually by changing `jdk.tls.disabledAlgorithms` in
146
208
  the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.1` needs to be removed from the list.
147
209
 
210
+ [id="plugins-{type}s-{plugin}-ssl_verification_mode"]
211
+ ===== `ssl_verification_mode`
212
+
213
+ * Value can be any of: `full`, `none`
214
+ * Default value is `full`
215
+
216
+ Defines how to verify the certificates presented by another part in the TLS connection:
217
+
218
+ `full` validates that the server certificate has an issue date that's within
219
+ the not_before and not_after dates; chains to a trusted Certificate Authority (CA), and
220
+ has a hostname or IP address that matches the names within the certificate.
221
+
222
+ `none` performs no certificate validation.
223
+
224
+ NOTE: This setting can be used only if <<plugins-{type}s-{plugin}-mode>> is `client`.
225
+
148
226
  [id="plugins-{type}s-{plugin}-ssl_verify"]
149
227
  ===== `ssl_verify`
228
+ deprecated[6.2.0, Replaced by <<plugins-{type}s-{plugin}-ssl_client_authentication>> and <<plugins-{type}s-{plugin}-ssl_verification_mode>>]
150
229
 
151
230
  * Value type is <<boolean,boolean>>
152
231
  * Default value is `false`
@@ -154,8 +233,6 @@ the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.
154
233
  Verify the identity of the other end of the SSL connection against the CA.
155
234
  For input, sets the field `sslsubject` to that of the client certificate.
156
235
 
157
-
158
-
159
236
  [id="plugins-{type}s-{plugin}-common-options"]
160
237
  include::{include_path}/{type}.asciidoc[]
161
238
 
@@ -3,6 +3,7 @@ require "logstash/outputs/base"
3
3
  require "logstash/namespace"
4
4
  require "thread"
5
5
  require "logstash/util/socket_peer"
6
+ require "logstash/plugin_mixins/normalize_config_support"
6
7
 
7
8
  # Write events over a TCP socket.
8
9
  #
@@ -12,6 +13,8 @@ require "logstash/util/socket_peer"
12
13
  # depending on `mode`.
13
14
  class LogStash::Outputs::Tcp < LogStash::Outputs::Base
14
15
 
16
+ include LogStash::PluginMixins::NormalizeConfigSupport
17
+
15
18
  config_name "tcp"
16
19
  concurrency :single
17
20
 
@@ -33,17 +36,41 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
33
36
  config :mode, :validate => ["server", "client"], :default => "client"
34
37
 
35
38
  # Enable SSL (must be set for other `ssl_` options to take effect).
36
- config :ssl_enable, :validate => :boolean, :default => false
39
+ config :ssl_enable, :validate => :boolean, :default => false, :deprecated => "Use 'ssl_enabled' instead."
40
+
41
+ # Enable SSL (must be set for other `ssl_` options to take effect).
42
+ config :ssl_enabled, :validate => :boolean, :default => false
43
+
44
+ # Controls the server’s behavior in regard to requesting a certificate from client connections.
45
+ # `none`: No client authentication
46
+ # `optional`: Requests a client certificate but the client is not required to present one.
47
+ # `required`: Forces a client to present a certificate.
48
+ # This option needs to be used with `ssl_certificate_authorities` and a defined list of CAs.
49
+ config :ssl_client_authentication, :validate => %w[none optional required], :default => 'none'
37
50
 
38
51
  # Verify the identity of the other end of the SSL connection against the CA.
39
52
  # For input, sets the field `sslsubject` to that of the client certificate.
40
- config :ssl_verify, :validate => :boolean, :default => false
53
+ config :ssl_verify, :validate => :boolean, :default => false, :deprecated => "Use 'ssl_client_authentication' when `mode` is 'server' or 'ssl_verification_mode' when mode is `client`"
54
+
55
+ # Options to verify the server's certificate.
56
+ # "full": validates that the provided certificate has an issue date that’s within the not_before and not_after dates;
57
+ # chains to a trusted Certificate Authority (CA); has a hostname or IP address that matches the names within the certificate.
58
+ # "certificate": Validates the provided certificate and verifies that it’s signed by a trusted authority (CA), but does’t check the certificate hostname.
59
+ # "none": performs no certificate validation. Disabling this severely compromises security (https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf)
60
+ config :ssl_verification_mode, :validate => %w[full none], :default => 'full'
41
61
 
42
62
  # The SSL CA certificate, chainfile or CA path. The system CA path is automatically included.
43
- config :ssl_cacert, :validate => :path
63
+ config :ssl_cacert, :validate => :path, :deprecated => "Use 'ssl_certificate_authorities' instead."
64
+
65
+ # Validate client certificate or certificate chain against these authorities. You can define multiple files.
66
+ # All the certificates will be read and added to the trust store.
67
+ config :ssl_certificate_authorities, :validate => :path, :list => true
68
+
69
+ # SSL certificate path
70
+ config :ssl_cert, :validate => :path, :deprecated => "Use 'ssl_certificate' instead."
44
71
 
45
72
  # SSL certificate path
46
- config :ssl_cert, :validate => :path
73
+ config :ssl_certificate, :validate => :path
47
74
 
48
75
  # SSL key path
49
76
  config :ssl_key, :validate => :path
@@ -54,6 +81,9 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
54
81
  # NOTE: the default setting [] uses SSL engine defaults
55
82
  config :ssl_supported_protocols, :validate => ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], :default => [], :list => true
56
83
 
84
+ # The list of ciphers suite to use
85
+ config :ssl_cipher_suites, :validate => :string, :list => true
86
+
57
87
  class Client
58
88
 
59
89
  ##
@@ -95,8 +125,8 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
95
125
  require "openssl"
96
126
 
97
127
  @ssl_context = new_ssl_context
98
- if @ssl_cert
99
- @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
128
+ if @ssl_certificate
129
+ @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_certificate))
100
130
  if @ssl_key
101
131
  # if we have an encrypted key and a password is not provided (nil) than OpenSSL::PKey::RSA
102
132
  # prompts the user to enter a password interactively - we do not want to do that,
@@ -104,17 +134,21 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
104
134
  @ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key), @ssl_key_passphrase.value || '')
105
135
  end
106
136
  end
107
- if @ssl_verify
108
- @cert_store = OpenSSL::X509::Store.new
109
- # Load the system default certificate path to the store
110
- @cert_store.set_default_paths
111
- if File.directory?(@ssl_cacert)
112
- @cert_store.add_path(@ssl_cacert)
137
+
138
+ @ssl_context.cert_store = load_cert_store
139
+ if server?
140
+ if @ssl_client_authentication == 'none'
141
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
142
+ else
143
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
144
+ @ssl_context.verify_mode |= OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT if @ssl_client_authentication == 'required'
145
+ end
146
+ else
147
+ if @ssl_verification_mode == 'none'
148
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
113
149
  else
114
- @cert_store.add_file(@ssl_cacert)
150
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
115
151
  end
116
- @ssl_context.cert_store = @cert_store
117
- @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
118
152
  end
119
153
 
120
154
  @ssl_context.min_version = :TLS1_1 # not strictly required - JVM should have disabled TLSv1
@@ -127,6 +161,9 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
127
161
  disabled_protocols.map! { |v| OpenSSL::SSL.const_get "OP_NO_#{v.sub('.', '_')}" }
128
162
  @ssl_context.options = disabled_protocols.reduce(@ssl_context.options, :|)
129
163
  end
164
+
165
+ @ssl_context.ciphers = @ssl_cipher_suites if @ssl_cipher_suites&.any?
166
+
130
167
  @ssl_context
131
168
  end
132
169
  private :setup_ssl
@@ -137,13 +174,36 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
137
174
  end
138
175
  private :new_ssl_context
139
176
 
177
+ def new_ssl_certificate_store
178
+ OpenSSL::X509::Store.new
179
+ end
180
+ private :new_ssl_certificate_store
181
+
182
+ def load_cert_store
183
+ cert_store = new_ssl_certificate_store
184
+ cert_store.set_default_paths
185
+ @ssl_certificate_authorities&.each do |cert|
186
+ cert_store.add_file(cert)
187
+ end
188
+ cert_store
189
+ end
190
+ private :load_cert_store
191
+
192
+ def initialize(*args)
193
+ super(*args)
194
+ setup_ssl_params!
195
+ end
196
+
140
197
  # @overload Base#register
141
198
  def register
142
199
  require "socket"
143
200
  require "stud/try"
201
+
202
+ validate_ssl_config!
203
+
144
204
  @closed = Concurrent::AtomicBoolean.new(false)
145
205
  @thread_no = Concurrent::AtomicFixnum.new(0)
146
- setup_ssl if @ssl_enable
206
+ setup_ssl if @ssl_enabled
147
207
 
148
208
  if server?
149
209
  run_as_server
@@ -160,20 +220,40 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
160
220
  @logger.error("Could not start tcp server: Address in use", host: @host, port: @port)
161
221
  raise
162
222
  end
163
- if @ssl_enable
223
+ if @ssl_enabled
164
224
  @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
165
- end # @ssl_enable
225
+ end # @ssl_enabled
166
226
  @client_threads = Concurrent::Array.new
167
227
 
168
228
  @accept_thread = Thread.new(@server_socket) do |server_socket|
169
229
  LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
170
230
  loop do
171
231
  break if @closed.value
172
- client_socket = server_socket.accept_nonblock exception: false
173
- if client_socket == :wait_readable
174
- IO.select [ server_socket ]
175
- next
232
+ # OpenSSL::SSL::SSLServer does not support the #accept_nonblock method.
233
+ # When SSL is enabled, it needs to use the blocking counterpart and ignore
234
+ # SSLError errors, as they may be client's issues such as missing client's
235
+ # certificates, ciphers, etc. If it's not rescued here, it would close the
236
+ # TCP server and exit the plugin.
237
+ # On the other hand, IOError should normally happen when the pipeline configuration
238
+ # is reloaded, as the stream gets closed in the thread
239
+ if @ssl_enabled
240
+ begin
241
+ client_socket = server_socket.accept
242
+ rescue OpenSSL::SSL::SSLError => e
243
+ log_warn("SSL Error", e)
244
+ retry unless @closed.value
245
+ rescue IOError => e
246
+ log_warn("IO Error", e)
247
+ retry unless @closed.value
248
+ end
249
+ else
250
+ client_socket = server_socket.accept_nonblock exception: false
251
+ if client_socket == :wait_readable
252
+ IO.select [ server_socket ]
253
+ next
254
+ end
176
255
  end
256
+
177
257
  Thread.start(client_socket) do |client_socket|
178
258
  # monkeypatch a 'peer' method onto the socket.
179
259
  client_socket.extend(::LogStash::Util::SocketPeer)
@@ -256,7 +336,7 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
256
336
  def connect
257
337
  begin
258
338
  client_socket = TCPSocket.new(@host, @port)
259
- if @ssl_enable
339
+ if @ssl_enabled
260
340
  client_socket = OpenSSL::SSL::SSLSocket.new(client_socket, @ssl_context)
261
341
  begin
262
342
  client_socket.connect
@@ -277,6 +357,95 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
277
357
  end
278
358
  end # def connect
279
359
 
360
+ def validate_ssl_config!
361
+ unless @ssl_enabled
362
+ ignored_ssl_settings = original_params.select { |k| k != 'ssl_enabled' && k != 'ssl_enable' && k.start_with?('ssl_') }
363
+ @logger.warn("Configured SSL settings are not used when `#{provided_ssl_enabled_config_name}` is set to `false`: #{ignored_ssl_settings.keys}") if ignored_ssl_settings.any?
364
+ return
365
+ end
366
+
367
+ if @ssl_certificate && !@ssl_key
368
+ raise LogStash::ConfigurationError, "Using an `ssl_certificate` requires an `ssl_key`"
369
+ elsif @ssl_key && !@ssl_certificate
370
+ raise LogStash::ConfigurationError, 'An `ssl_certificate` is required when using an `ssl_key`'
371
+ end
372
+
373
+ if server?
374
+ validate_server_ssl_config!
375
+ else
376
+ validate_client_ssl_config!
377
+ end
378
+ end
379
+
380
+ def validate_client_ssl_config!
381
+ if original_params.include?('ssl_client_authentication')
382
+ raise LogStash::ConfigurationError, "`ssl_client_authentication` must not be configured when mode is `client`, use `ssl_verification_mode` instead."
383
+ end
384
+ end
385
+
386
+ def validate_server_ssl_config!
387
+ if original_params.include?('ssl_verification_mode')
388
+ raise LogStash::ConfigurationError, "`ssl_verification_mode` must not be configured when mode is `server`, use `ssl_client_authentication` instead."
389
+ end
390
+
391
+ if @ssl_certificate.nil?
392
+ raise LogStash::ConfigurationError, "An `ssl_certificate` is required when `#{provided_ssl_enabled_config_name}` => true"
393
+ end
394
+
395
+ if requires_ssl_certificate_authorities? && (@ssl_certificate_authorities.nil? || @ssl_certificate_authorities.empty?)
396
+ raise LogStash::ConfigurationError, "An `ssl_certificate_authorities` is required when `ssl_client_authentication` => `#{@ssl_client_authentication}`"
397
+ end
398
+ end
399
+
400
+ def requires_ssl_certificate_authorities?
401
+ original_params.include?('ssl_client_authentication') && @ssl_client_authentication != 'none'
402
+ end
403
+
404
+ def provided_ssl_enabled_config_name
405
+ original_params.include?('ssl_enable') ? 'ssl_enable' : 'ssl_enabled'
406
+ end
407
+
408
+ def setup_ssl_params!
409
+ @ssl_enabled = normalize_config(:ssl_enabled) do |normalizer|
410
+ normalizer.with_deprecated_alias(:ssl_enable)
411
+ end
412
+
413
+ @ssl_certificate = normalize_config(:ssl_certificate) do |normalizer|
414
+ normalizer.with_deprecated_alias(:ssl_cert)
415
+ end
416
+
417
+ if server?
418
+ @ssl_client_authentication = normalize_config(:ssl_client_authentication) do |normalizer|
419
+ normalizer.with_deprecated_mapping(:ssl_verify) do |ssl_verify|
420
+ ssl_verify == true ? 'required' : 'none'
421
+ end
422
+ end
423
+ else
424
+ @ssl_verification_mode = normalize_config(:ssl_verification_mode) do |normalize|
425
+ normalize.with_deprecated_mapping(:ssl_verify) do |ssl_verify|
426
+ ssl_verify == true ? 'full' : 'none'
427
+ end
428
+ end
429
+
430
+ # Keep backwards compatibility with the default :ssl_verify value (false)
431
+ if !original_params.include?('ssl_verify') && !original_params.include?('ssl_verification_mode')
432
+ @ssl_verification_mode = 'none'
433
+ end
434
+ end
435
+
436
+ @ssl_certificate_authorities = normalize_config(:ssl_certificate_authorities) do |normalize|
437
+ normalize.with_deprecated_mapping(:ssl_cacert) do |ssl_cacert|
438
+ if File.directory?(ssl_cacert)
439
+ Dir.children(ssl_cacert)
440
+ .map{ |f| File.join(ssl_cacert, f) }
441
+ .reject{ |f| File.directory?(f) || File.basename(f).start_with?('.') }
442
+ else
443
+ [ssl_cacert]
444
+ end
445
+ end
446
+ end
447
+ end
448
+
280
449
  def server?
281
450
  @mode == "server"
282
451
  end # def server?
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-output-tcp'
4
- s.version = '6.1.2'
4
+ s.version = '6.2.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Writes events over a TCP socket"
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"
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_runtime_dependency 'logstash-core', '>= 8.1.0'
25
25
  s.add_runtime_dependency 'logstash-codec-json'
26
26
  s.add_runtime_dependency 'stud'
27
+ s.add_runtime_dependency 'logstash-mixin-normalize_config_support', '~>1.0'
27
28
 
28
29
  s.add_runtime_dependency 'jruby-openssl', '>= 0.12.2' # 0.12 supports TLSv1.3
29
30
 
@@ -108,6 +108,20 @@ describe LogStash::Outputs::Tcp do
108
108
  end
109
109
  end
110
110
 
111
+ context "with cipher suites" do
112
+ let(:config) do
113
+ super().merge('ssl_cipher_suites' => %w[TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA])
114
+ end
115
+
116
+ it "limits the ciphers selection" do
117
+ ssl_context = OpenSSL::SSL::SSLContext.new
118
+ allow(subject).to receive(:new_ssl_context).and_return(ssl_context)
119
+ subject.send :setup_ssl
120
+ expect(ssl_context.ciphers.length).to eq(2)
121
+ expect(ssl_context.ciphers).to satisfy { |arr| arr[0].include?('AES128-GCM-SHA256') && arr[1].include?('EDH-RSA-DES-CBC3-SHA') }
122
+ end
123
+ end
124
+
111
125
  context "client mode" do
112
126
  before { subject.register }
113
127
 
@@ -136,7 +150,7 @@ describe LogStash::Outputs::Tcp do
136
150
  end
137
151
 
138
152
  context "when enabling SSL" do
139
- let(:config) { super().merge("ssl_enable" => true, 'codec' => 'plain') }
153
+ let(:config) { super().merge("ssl_enabled" => true, 'codec' => 'plain') }
140
154
  context "and not providing a certificate/key pair" do
141
155
  it "registers without error" do
142
156
  expect { subject.register }.to_not raise_error
@@ -145,13 +159,18 @@ describe LogStash::Outputs::Tcp do
145
159
 
146
160
  context "and providing a certificate/key pair" do
147
161
  let(:cert_key_pair) { Flores::PKI.generate }
148
- let(:certificate) { cert_key_pair.first }
149
- let(:cert_file) do
150
- path = Tempfile.new('foo').path
151
- IO.write(path, certificate.to_s)
162
+ let(:certificate) do
163
+ path = Tempfile.new('certificate').path
164
+ IO.write(path, cert_key_pair.first.to_s)
152
165
  path
153
166
  end
154
- let(:config) { super().merge("ssl_cert" => cert_file) }
167
+ let(:key) do
168
+ path = Tempfile.new('key').path
169
+ IO.write(path, cert_key_pair[1].to_s)
170
+ path
171
+ end
172
+ let(:config) { super().merge("ssl_certificate" => certificate, "ssl_key" => key) }
173
+
155
174
  it "registers without error" do
156
175
  expect { subject.register }.to_not raise_error
157
176
  end
@@ -162,7 +181,7 @@ describe LogStash::Outputs::Tcp do
162
181
  context "ES generated plain-text certificate/key" do
163
182
  let(:key_file) { File.join(FIXTURES_PATH, 'plaintext/instance.key') }
164
183
  let(:crt_file) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
165
- let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
184
+ let(:config) { super().merge("ssl_certificate" => crt_file, "ssl_key" => key_file) }
166
185
 
167
186
  it "registers without error" do
168
187
  expect { subject.register }.to_not raise_error
@@ -238,7 +257,7 @@ describe LogStash::Outputs::Tcp do
238
257
  context "encrypted key using PKCS#1" do
239
258
  let(:key_file) { File.join(FIXTURES_PATH, 'encrypted/instance.key') }
240
259
  let(:crt_file) { File.join(FIXTURES_PATH, 'encrypted/instance.crt') }
241
- let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
260
+ let(:config) { super().merge("ssl_certificate" => crt_file, "ssl_key" => key_file) }
242
261
 
243
262
  it "registers with error (due missing password)" do
244
263
  expect { subject.register }.to raise_error(OpenSSL::PKey::RSAError) # TODO need a better error
@@ -258,7 +277,7 @@ describe LogStash::Outputs::Tcp do
258
277
  context "and protocol is TLSv1.3" do
259
278
  let(:key_file) { File.join(FIXTURES_PATH, 'plaintext/instance.key') }
260
279
  let(:crt_file) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
261
- let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
280
+ let(:config) { super().merge("ssl_certificate" => crt_file, "ssl_key" => key_file) }
262
281
 
263
282
  let(:secure_server) do
264
283
  ssl_context = OpenSSL::SSL::SSLContext.new
@@ -307,5 +326,171 @@ describe LogStash::Outputs::Tcp do
307
326
  expect(buffer).to eq(message * 2)
308
327
  end
309
328
  end
329
+
330
+ context "with only ssl_certificate set" do
331
+ let(:config) { super().merge("ssl_certificate" => File.join(FIXTURES_PATH, 'plaintext/instance.crt')) }
332
+
333
+ it "should raise a configuration error to request also `ssl_key`" do
334
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /Using an `ssl_certificate` requires an `ssl_key`/)
335
+ end
336
+ end
337
+
338
+ context "with only ssl_key set" do
339
+ let(:config) { super().merge("ssl_key" => File.join(FIXTURES_PATH, 'plaintext/instance.key')) }
340
+
341
+ it "should raise a configuration error to request also `ssl_key`" do
342
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /An `ssl_certificate` is required when using an `ssl_key`/)
343
+ end
344
+ end
345
+
346
+ context "and mode is server" do
347
+ let(:config) do
348
+ {
349
+ "host" => "127.0.0.1",
350
+ "port" => port,
351
+ "mode" => 'server',
352
+ "ssl_enabled" => true,
353
+ "ssl_certificate" => File.join(FIXTURES_PATH, 'plaintext/instance.crt'),
354
+ "ssl_key" => File.join(FIXTURES_PATH, 'plaintext/instance.key'),
355
+ }
356
+ end
357
+
358
+ context "with no ssl_certificate" do
359
+ let(:config) { super().reject { |k| "ssl_key".eql?(k) || "ssl_certificate".eql?(k) } }
360
+
361
+ it "should raise a configuration error" do
362
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /An `ssl_certificate` is required when `ssl_enabled` => true/)
363
+ end
364
+ end
365
+
366
+ context "with ssl_client_authentication = `none` and no ssl_certificate_authorities" do
367
+ let(:config) { super().merge(
368
+ 'ssl_client_authentication' => 'none',
369
+ 'ssl_certificate_authorities' => []
370
+ ) }
371
+
372
+ it "should register without errors" do
373
+ expect { subject.register }.to_not raise_error
374
+ end
375
+ end
376
+
377
+ context "with deprecated ssl_verify = true and no ssl_certificate_authorities" do
378
+ let(:config) { super().merge(
379
+ 'ssl_verify' => true,
380
+ 'ssl_certificate_authorities' => []
381
+ ) }
382
+
383
+ it "should register without errors" do
384
+ expect { subject.register }.to_not raise_error
385
+ end
386
+ end
387
+
388
+ %w[required optional].each do |ssl_client_authentication|
389
+ context "with ssl_client_authentication = `#{ssl_client_authentication}` and no ssl_certificate_authorities" do
390
+ let(:config) { super().merge(
391
+ 'ssl_client_authentication' => ssl_client_authentication,
392
+ 'ssl_certificate_authorities' => []
393
+ ) }
394
+
395
+ it "should raise a configuration error" do
396
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /An `ssl_certificate_authorities` is required when `ssl_client_authentication` => `#{ssl_client_authentication}`/)
397
+ end
398
+ end
399
+ end
400
+
401
+ context "with ssl_verification_mode" do
402
+ let(:config) do
403
+ super().merge 'ssl_verification_mode' => 'full'
404
+ end
405
+
406
+ it "should raise a configuration error" do
407
+ expect{subject.register}.to raise_error(LogStash::ConfigurationError, /`ssl_verification_mode` must not be configured when mode is `server`, use `ssl_client_authentication` instead/)
408
+ end
409
+ end
410
+ end
411
+
412
+ context "with deprecated settings" do
413
+ let(:ssl_verify) { true }
414
+ let(:certificate_path) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
415
+ let(:config) do
416
+ {
417
+ "host" => "127.0.0.1",
418
+ "port" => port,
419
+ "ssl_enable" => true,
420
+ "ssl_cert" => certificate_path,
421
+ "ssl_key" => File.join(FIXTURES_PATH, 'plaintext/instance.key'),
422
+ "ssl_verify" => ssl_verify
423
+ }
424
+ end
425
+
426
+ context "and mode is server" do
427
+ let(:config) { super().merge("mode" => 'server') }
428
+ [true, false].each do |verify|
429
+ context "and ssl_verify is #{verify}" do
430
+ let(:ssl_verify) { verify }
431
+
432
+ it "should set new configs variables" do
433
+ subject.register
434
+ expect(subject.instance_variable_get(:@ssl_enabled)).to eql(true)
435
+ expect(subject.instance_variable_get(:@ssl_client_authentication)).to eql(verify ? 'required' : 'none')
436
+ expect(subject.instance_variable_get(:@ssl_certificate)).to eql(certificate_path)
437
+ end
438
+ end
439
+ end
440
+ end
441
+
442
+ context "and mode is client" do
443
+ let(:config) { super().merge("mode" => 'client') }
444
+ [true, false].each do |verify|
445
+ context "and ssl_verify is #{verify}" do
446
+ let(:ssl_verify) { verify }
447
+
448
+ it "should set new configs variables" do
449
+ subject.register
450
+ expect(subject.instance_variable_get(:@ssl_enabled)).to eql(true)
451
+ expect(subject.instance_variable_get(:@ssl_verification_mode)).to eql(verify ? 'full' : 'none')
452
+ expect(subject.instance_variable_get(:@ssl_certificate)).to eql(certificate_path)
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end
458
+
459
+ context "with ssl_client_authentication" do
460
+ let(:config) do
461
+ super().merge 'ssl_client_authentication' => 'required'
462
+ end
463
+
464
+ it "should raise a configuration error" do
465
+ expect{subject.register}.to raise_error(LogStash::ConfigurationError, /`ssl_client_authentication` must not be configured when mode is `client`, use `ssl_verification_mode` instead/)
466
+ end
467
+ end
468
+
469
+ context "with ssl_certificate_authorities" do
470
+ let(:certificate_path) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
471
+ let(:config) do
472
+ super().merge('ssl_certificate_authorities' => [certificate_path])
473
+ end
474
+
475
+ it "sets cert_store values" do
476
+ ssl_store = double(OpenSSL::X509::Store.new)
477
+ allow(ssl_store).to receive(:set_default_paths)
478
+ allow(ssl_store).to receive(:add_file)
479
+ allow(subject).to receive(:new_ssl_certificate_store).and_return(ssl_store)
480
+ subject.send :setup_ssl
481
+ expect(ssl_store).to have_received(:add_file).with(certificate_path)
482
+ end
483
+ end
484
+
485
+ context "CAs certificates" do
486
+ it "includes openssl default paths" do
487
+ ssl_store = double(OpenSSL::X509::Store.new)
488
+ allow(ssl_store).to receive(:set_default_paths)
489
+ allow(subject).to receive(:new_ssl_certificate_store).and_return(ssl_store)
490
+ subject.send :setup_ssl
491
+ expect(ssl_store).to have_received(:set_default_paths)
492
+ end
493
+ end
494
+
310
495
  end
311
496
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-tcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.2
4
+ version: 6.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-23 00:00:00.000000000 Z
11
+ date: 2023-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +72,20 @@ dependencies:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.0'
81
+ name: logstash-mixin-normalize_config_support
82
+ prerelease: false
83
+ type: :runtime
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
75
89
  - !ruby/object:Gem::Dependency
76
90
  requirement: !ruby/object:Gem::Requirement
77
91
  requirements: