logstash-output-tcp 6.1.2 → 6.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/docs/index.asciidoc +83 -6
- data/lib/logstash/outputs/tcp.rb +192 -23
- data/logstash-output-tcp.gemspec +2 -1
- data/spec/outputs/tcp_spec.rb +194 -9
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12843c570af7ef789c697435e55375ecda6d6ed85a63181ae0501cc21298bbeb
|
4
|
+
data.tar.gz: 95a41abe82bb81210646428fb6d228a390a7ba87043e817dab5a75b19405195e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50d788801e0d243a7e3ba77cf4c96fb31e5a273656a99a6f5b5714076cb1bc4a5a0b5c3dcf8b3d36c7402d38a442129ebaf72b1f9cce7ec4dd22a532a7cd72ec
|
7
|
+
data.tar.gz: 43e9a8f1c6e401801eb8ead0fc6773ff39c3f2efa0f112cbf63075050769f2acb09f870734001c9678ee679fd2f633243c22cdb3d4cf8455ea6ae69f672d5fcc
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 6.2.1
|
2
|
+
- Document correct default plugin codec [#54](https://github.com/logstash-plugins/logstash-output-tcp/pull/54)
|
3
|
+
|
4
|
+
## 6.2.0
|
5
|
+
- Reviewed and deprecated SSL settings to comply with Logstash's naming convention [#53](https://github.com/logstash-plugins/logstash-output-tcp/pull/53)
|
6
|
+
- Deprecated `ssl_enable` in favor of `ssl_enabled`
|
7
|
+
- Deprecated `ssl_cert` in favor of `ssl_certificate`
|
8
|
+
- Deprecated `ssl_verify` in favor of `ssl_client_authentication` when mode is `server`
|
9
|
+
- Deprecated `ssl_verify` in favor of `ssl_verification_mode` when mode is `client`
|
10
|
+
- Added `ssl_cipher_suites` configuration
|
11
|
+
- Added SSL configuration validations
|
12
|
+
|
1
13
|
## 6.1.2
|
2
14
|
- Changed the client mode to write using the non-blocking method. [#52](https://github.com/logstash-plugins/logstash-output-tcp/pull/52)
|
3
15
|
|
data/docs/index.asciidoc
CHANGED
@@ -23,7 +23,7 @@ include::{include_path}/plugin_header.asciidoc[]
|
|
23
23
|
|
24
24
|
Write events over a TCP socket.
|
25
25
|
|
26
|
-
|
26
|
+
By default this plugin uses the `json` codec. In order to have each event json separated by a newline, use the `json_lines` codec.
|
27
27
|
|
28
28
|
Can either accept connections from clients or connect to a server,
|
29
29
|
depending on `mode`.
|
@@ -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|
|
44
|
-
| <<plugins-{type}s-{plugin}-ssl_cert>> |a valid filesystem path|
|
45
|
-
| <<plugins-{type}s-{plugin}-
|
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
|
|
data/lib/logstash/outputs/tcp.rb
CHANGED
@@ -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 :
|
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 @
|
99
|
-
@ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
@
|
111
|
-
|
112
|
-
|
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
|
-
@
|
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 @
|
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 @
|
223
|
+
if @ssl_enabled
|
164
224
|
@server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
|
165
|
-
end # @
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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 @
|
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?
|
data/logstash-output-tcp.gemspec
CHANGED
@@ -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
|
4
|
+
s.version = '6.2.1'
|
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
|
|
data/spec/outputs/tcp_spec.rb
CHANGED
@@ -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("
|
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)
|
149
|
-
|
150
|
-
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(:
|
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("
|
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("
|
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("
|
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
|
4
|
+
version: 6.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-05 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:
|