Skip to content

Commit 7d2b3f6

Browse files
kareskarenzone
andauthored
Feat: added ssl_supported_protocols option (#131)
Co-authored-by: Karen Metts <35154725+karenzone@users.noreply.github.com>
1 parent 6dca44e commit 7d2b3f6

File tree

6 files changed

+213
-117
lines changed

6 files changed

+213
-117
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 5.5.0
2+
- Feat: added `ssl_supported_protocols` option [#131](https://github.com/logstash-plugins/logstash-output-http/pull/131)
3+
14
## 5.4.1
25
- Fix retry indefinitely in termination process. This feature requires Logstash 8.1 [#129](https://github.com/logstash-plugins/logstash-output-http/pull/129)
36
- Docs: Add retry policy description [#130](https://github.com/logstash-plugins/logstash-output-http/pull/130)

docs/index.asciidoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
100100
| <<plugins-{type}s-{plugin}-retry_non_idempotent>> |<<boolean,boolean>>|No
101101
| <<plugins-{type}s-{plugin}-retryable_codes>> |<<number,number>>|No
102102
| <<plugins-{type}s-{plugin}-socket_timeout>> |<<number,number>>|No
103+
| <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
103104
| <<plugins-{type}s-{plugin}-ssl_verification_mode>> |<<string,string>>|No
104105
| <<plugins-{type}s-{plugin}-truststore>> |a valid filesystem path|No
105106
| <<plugins-{type}s-{plugin}-truststore_password>> |<<password,password>>|No
@@ -378,6 +379,23 @@ See <<plugins-{type}s-{plugin}-retry_policy,Retry Policy>> for more information.
378379

379380
Timeout (in seconds) to wait for data on the socket. Default is `10s`
380381

382+
[id="plugins-{type}s-{plugin}-ssl_supported_protocols"]
383+
===== `ssl_supported_protocols`
384+
385+
* Value type is <<string,string>>
386+
* Allowed values are: `'TLSv1.1'`, `'TLSv1.2'`, `'TLSv1.3'`
387+
* Default depends on the JDK being used. With up-to-date Logstash, the default is `['TLSv1.2', 'TLSv1.3']`.
388+
`'TLSv1.1'` is not considered secure and is only provided for legacy applications.
389+
390+
List of allowed SSL/TLS versions to use when establishing a connection to the HTTP endpoint.
391+
392+
For Java 8 `'TLSv1.3'` is supported only since **8u262** (AdoptOpenJDK), but requires that you set the
393+
`LS_JAVA_OPTS="-Djdk.tls.client.protocols=TLSv1.3"` system property in Logstash.
394+
395+
NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as the one packaged with Logstash,
396+
the protocol is disabled by default and needs to be enabled manually by changing `jdk.tls.disabledAlgorithms` in
397+
the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.1` needs to be removed from the list.
398+
381399
[id="plugins-{type}s-{plugin}-ssl_verification_mode"]
382400
===== `ssl_verification_mode`
383401

lib/logstash/outputs/http.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def send_event(event, attempt)
272272
:url => url,
273273
:method => @http_method,
274274
:message => exception.message,
275-
:class => exception.class.name,
275+
:class => exception.class,
276276
:will_retry => will_retry
277277
}
278278
if @logger.debug?

logstash-output-http.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Gem::Specification.new do |s|
22
s.name = 'logstash-output-http'
3-
s.version = '5.4.1'
3+
s.version = '5.5.0'
44
s.licenses = ['Apache License (2.0)']
55
s.summary = "Sends events to a generic HTTP or HTTPS endpoint"
66
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"
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
2020

2121
# Gem dependencies
2222
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
23-
s.add_runtime_dependency "logstash-mixin-http_client", ">= 7.1.0", "< 8.0.0"
23+
s.add_runtime_dependency "logstash-mixin-http_client", ">= 7.2.0", "< 8.0.0"
2424

2525
s.add_development_dependency 'logstash-devutils'
2626
s.add_development_dependency 'sinatra'

spec/outputs/http_spec.rb

Lines changed: 53 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,4 @@
1-
require "logstash/devutils/rspec/spec_helper"
2-
require "logstash/outputs/http"
3-
require "logstash/codecs/plain"
4-
require "thread"
5-
require "sinatra"
6-
require "webrick"
7-
require "webrick/https"
8-
require 'openssl'
9-
require_relative "../supports/compressed_requests"
10-
11-
PORT = rand(65535-1024) + 1025
12-
13-
class LogStash::Outputs::Http
14-
attr_writer :agent
15-
attr_reader :request_tokens
16-
end
17-
18-
# note that Sinatra startup and shutdown messages are directly logged to stderr so
19-
# it is not really possible to disable them without reopening stderr which is not advisable.
20-
#
21-
# == Sinatra (v1.4.6) has taken the stage on 51572 for development with backup from WEBrick
22-
# == Sinatra has ended his set (crowd applauds)
23-
#
24-
class TestApp < Sinatra::Base
25-
# on the fly uncompress gzip content
26-
use CompressedRequests
27-
28-
set :environment, :production
29-
set :sessions, false
30-
31-
@@server_settings = {
32-
:AccessLog => [], # disable WEBrick logging
33-
:Logger => WEBrick::BasicLog::new(nil, WEBrick::BasicLog::FATAL)
34-
}
35-
36-
def self.server_settings
37-
@@server_settings
38-
end
39-
40-
def self.server_settings=(settings)
41-
@@server_settings = settings
42-
end
43-
44-
def self.multiroute(methods, path, &block)
45-
methods.each do |method|
46-
method.to_sym
47-
self.send method, path, &block
48-
end
49-
end
50-
51-
def self.last_request=(request)
52-
@last_request = request
53-
end
54-
55-
def self.last_request
56-
@last_request
57-
end
58-
59-
def self.retry_fail_count=(count)
60-
@retry_fail_count = count
61-
end
62-
63-
def self.retry_fail_count()
64-
@retry_fail_count || 2
65-
end
66-
67-
multiroute(%w(get post put patch delete), "/good") do
68-
self.class.last_request = request
69-
[200, "YUP"]
70-
end
71-
72-
multiroute(%w(get post put patch delete), "/bad") do
73-
self.class.last_request = request
74-
[400, "YUP"]
75-
end
76-
77-
multiroute(%w(get post put patch delete), "/retry") do
78-
self.class.last_request = request
79-
80-
if self.class.retry_fail_count > 0
81-
self.class.retry_fail_count -= 1
82-
[429, "Will succeed in #{self.class.retry_fail_count}"]
83-
else
84-
[200, "Done Retrying"]
85-
end
86-
end
87-
end
88-
89-
RSpec.configure do
90-
#http://stackoverflow.com/questions/6557079/start-and-call-ruby-http-server-in-the-same-script
91-
def start_app_and_wait(app, opts = {})
92-
queue = Queue.new
93-
94-
Thread.start do
95-
begin
96-
app.start!({ server: 'WEBrick', port: PORT }.merge opts) do |server|
97-
queue.push(server)
98-
end
99-
rescue => e
100-
warn "Error starting app: #{e.inspect}" # ignore
101-
end
102-
end
103-
104-
queue.pop # blocks until the start! callback runs
105-
end
106-
end
1+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
1072

1083
describe LogStash::Outputs::Http do
1094
# Wait for the async request to finish in this spinlock
@@ -520,24 +415,28 @@ def start_app_and_wait(app, opts = {})
520415
end
521416
end
522417

523-
describe LogStash::Outputs::Http do # different block as we're starting web server with TLS
418+
RSpec.describe LogStash::Outputs::Http do # different block as we're starting web server with TLS
524419

525420
@@default_server_settings = TestApp.server_settings.dup
526421

527422
before do
528-
cert, key = WEBrick::Utils.create_self_signed_cert 2048, [["CN", ssl_cert_host]], "Logstash testing"
529-
TestApp.server_settings = @@default_server_settings.merge({
530-
:SSLEnable => true,
531-
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
532-
:SSLCertificate => cert,
533-
:SSLPrivateKey => key
534-
})
423+
TestApp.server_settings = @@default_server_settings.merge(webrick_config)
535424

536425
TestApp.last_request = nil
537426

538427
@server = start_app_and_wait(TestApp)
539428
end
540429

430+
let(:webrick_config) do
431+
cert, key = WEBrick::Utils.create_self_signed_cert 2048, [["CN", ssl_cert_host]], "Logstash testing"
432+
{
433+
SSLEnable: true,
434+
SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
435+
SSLCertificate: cert,
436+
SSLPrivateKey: key
437+
}
438+
end
439+
541440
after do
542441
@server.shutdown # WEBrick::HTTPServer
543442

@@ -590,4 +489,44 @@ def start_app_and_wait(app, opts = {})
590489

591490
end
592491

492+
context 'with supported_protocols set to (disabled) 1.1' do
493+
494+
let(:config) { super().merge 'ssl_supported_protocols' => ['TLSv1.1'], 'ssl_verification_mode' => 'none' }
495+
496+
it "keeps retrying due a protocol exception" do # TLSv1.1 not enabled by default
497+
expect(subject).to receive(:log_failure).
498+
with('Could not fetch URL', hash_including(message: 'No appropriate protocol (protocol is disabled or cipher suites are inappropriate)')).
499+
at_least(:once)
500+
Thread.start { subject.multi_receive [ event ] }
501+
sleep 1.0
502+
end
503+
504+
end unless tls_version_enabled_by_default?('TLSv1.1')
505+
506+
context 'with supported_protocols set to 1.2/1.3' do
507+
508+
let(:config) { super().merge 'ssl_supported_protocols' => ['TLSv1.2', 'TLSv1.3'], 'ssl_verification_mode' => 'none' }
509+
510+
let(:webrick_config) { super().merge SSLVersion: 'TLSv1.2' }
511+
512+
it "should process the request" do
513+
subject.multi_receive [ event ]
514+
expect(last_request_body).to include '"message":"hello!"'
515+
end
516+
517+
end
518+
519+
context 'with supported_protocols set to 1.3' do
520+
521+
let(:config) { super().merge 'ssl_supported_protocols' => ['TLSv1.3'], 'ssl_verification_mode' => 'none' }
522+
523+
let(:webrick_config) { super().merge SSLVersion: 'TLSv1.3' }
524+
525+
it "should process the request" do
526+
subject.multi_receive [ event ]
527+
expect(last_request_body).to include '"message":"hello!"'
528+
end
529+
530+
end if tls_version_enabled_by_default?('TLSv1.3') && JOpenSSL::VERSION > '0.12' # due WEBrick uses OpenSSL
531+
593532
end

spec/spec_helper.rb

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
require "logstash/devutils/rspec/spec_helper"
2+
require "logstash/outputs/http"
3+
require "logstash/codecs/plain"
4+
5+
require "thread"
6+
require "sinatra"
7+
require "webrick"
8+
require "webrick/https"
9+
require 'openssl'
10+
11+
require "supports/compressed_requests"
12+
13+
PORT = rand(65535-1024) + 1025
14+
15+
class LogStash::Outputs::Http
16+
attr_writer :agent
17+
attr_reader :request_tokens
18+
end
19+
20+
# NOTE: extend WEBrick with support for config[:SSLVersion]
21+
WEBrick::GenericServer.class_eval do
22+
alias_method :__setup_ssl_context, :setup_ssl_context
23+
24+
def setup_ssl_context(config)
25+
ctx = __setup_ssl_context(config)
26+
ctx.ssl_version = config[:SSLVersion] if config[:SSLVersion]
27+
ctx
28+
end
29+
30+
end
31+
32+
# note that Sinatra startup and shutdown messages are directly logged to stderr so
33+
# it is not really possible to disable them without reopening stderr which is not advisable.
34+
#
35+
# == Sinatra (v1.4.6) has taken the stage on 51572 for development with backup from WEBrick
36+
# == Sinatra has ended his set (crowd applauds)
37+
#
38+
class TestApp < Sinatra::Base
39+
# on the fly uncompress gzip content
40+
use CompressedRequests
41+
42+
set :environment, :production
43+
set :sessions, false
44+
45+
@@server_settings = {
46+
:AccessLog => [], # disable WEBrick logging
47+
:Logger => WEBrick::BasicLog::new(nil, WEBrick::BasicLog::FATAL)
48+
}
49+
50+
def self.server_settings
51+
@@server_settings
52+
end
53+
54+
def self.server_settings=(settings)
55+
@@server_settings = settings
56+
end
57+
58+
def self.multiroute(methods, path, &block)
59+
methods.each do |method|
60+
method.to_sym
61+
self.send method, path, &block
62+
end
63+
end
64+
65+
def self.last_request=(request)
66+
@last_request = request
67+
end
68+
69+
def self.last_request
70+
@last_request
71+
end
72+
73+
def self.retry_fail_count=(count)
74+
@retry_fail_count = count
75+
end
76+
77+
def self.retry_fail_count()
78+
@retry_fail_count || 2
79+
end
80+
81+
multiroute(%w(get post put patch delete), "/good") do
82+
self.class.last_request = request
83+
[200, "YUP"]
84+
end
85+
86+
multiroute(%w(get post put patch delete), "/bad") do
87+
self.class.last_request = request
88+
[400, "YUP"]
89+
end
90+
91+
multiroute(%w(get post put patch delete), "/retry") do
92+
self.class.last_request = request
93+
94+
if self.class.retry_fail_count > 0
95+
self.class.retry_fail_count -= 1
96+
[429, "Will succeed in #{self.class.retry_fail_count}"]
97+
else
98+
[200, "Done Retrying"]
99+
end
100+
end
101+
end
102+
103+
RSpec.configure do |config|
104+
#http://stackoverflow.com/questions/6557079/start-and-call-ruby-http-server-in-the-same-script
105+
def start_app_and_wait(app, opts = {})
106+
queue = Queue.new
107+
108+
Thread.start do
109+
begin
110+
app.start!({ server: 'WEBrick', port: PORT }.merge opts) do |server|
111+
yield(server) if block_given?
112+
queue.push(server)
113+
end
114+
rescue => e
115+
warn "Error starting app: #{e.inspect}" # ignore
116+
end
117+
end
118+
119+
queue.pop # blocks until the start! callback runs
120+
end
121+
122+
config.extend(Module.new do
123+
124+
def tls_version_enabled_by_default?(tls_version)
125+
begin
126+
context = javax.net.ssl.SSLContext.getInstance('TLS')
127+
context.init nil, nil, nil
128+
context.getDefaultSSLParameters.getProtocols.include? tls_version.to_s
129+
rescue => e
130+
warn "#{__method__} failed : #{e.inspect}"
131+
nil
132+
end
133+
end
134+
135+
end)
136+
end

0 commit comments

Comments
 (0)