Skip to content

Commit

Permalink
Feat: added ssl_supported_protocols option (#131)
Browse files Browse the repository at this point in the history
Co-authored-by: Karen Metts <[email protected]>
  • Loading branch information
kares and karenzone authored Mar 28, 2022
1 parent 6dca44e commit 7d2b3f6
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 117 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 5.5.0
- Feat: added `ssl_supported_protocols` option [#131](https://github.com/logstash-plugins/logstash-output-http/pull/131)

## 5.4.1
- Fix retry indefinitely in termination process. This feature requires Logstash 8.1 [#129](https://github.com/logstash-plugins/logstash-output-http/pull/129)
- Docs: Add retry policy description [#130](https://github.com/logstash-plugins/logstash-output-http/pull/130)
Expand Down
18 changes: 18 additions & 0 deletions docs/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
| <<plugins-{type}s-{plugin}-retry_non_idempotent>> |<<boolean,boolean>>|No
| <<plugins-{type}s-{plugin}-retryable_codes>> |<<number,number>>|No
| <<plugins-{type}s-{plugin}-socket_timeout>> |<<number,number>>|No
| <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
| <<plugins-{type}s-{plugin}-ssl_verification_mode>> |<<string,string>>|No
| <<plugins-{type}s-{plugin}-truststore>> |a valid filesystem path|No
| <<plugins-{type}s-{plugin}-truststore_password>> |<<password,password>>|No
Expand Down Expand Up @@ -378,6 +379,23 @@ See <<plugins-{type}s-{plugin}-retry_policy,Retry Policy>> for more information.

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

[id="plugins-{type}s-{plugin}-ssl_supported_protocols"]
===== `ssl_supported_protocols`

* Value type is <<string,string>>
* Allowed values are: `'TLSv1.1'`, `'TLSv1.2'`, `'TLSv1.3'`
* Default depends on the JDK being used. With up-to-date Logstash, the default is `['TLSv1.2', 'TLSv1.3']`.
`'TLSv1.1'` is not considered secure and is only provided for legacy applications.

List of allowed SSL/TLS versions to use when establishing a connection to the HTTP endpoint.

For Java 8 `'TLSv1.3'` is supported only since **8u262** (AdoptOpenJDK), but requires that you set the
`LS_JAVA_OPTS="-Djdk.tls.client.protocols=TLSv1.3"` system property in Logstash.

NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as the one packaged with Logstash,
the protocol is disabled by default and needs to be enabled manually by changing `jdk.tls.disabledAlgorithms` in
the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.1` needs to be removed from the list.

[id="plugins-{type}s-{plugin}-ssl_verification_mode"]
===== `ssl_verification_mode`

Expand Down
2 changes: 1 addition & 1 deletion lib/logstash/outputs/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def send_event(event, attempt)
:url => url,
:method => @http_method,
:message => exception.message,
:class => exception.class.name,
:class => exception.class,
:will_retry => will_retry
}
if @logger.debug?
Expand Down
4 changes: 2 additions & 2 deletions logstash-output-http.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'logstash-output-http'
s.version = '5.4.1'
s.version = '5.5.0'
s.licenses = ['Apache License (2.0)']
s.summary = "Sends events to a generic HTTP or HTTPS endpoint"
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"
Expand All @@ -20,7 +20,7 @@ Gem::Specification.new do |s|

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

s.add_development_dependency 'logstash-devutils'
s.add_development_dependency 'sinatra'
Expand Down
167 changes: 53 additions & 114 deletions spec/outputs/http_spec.rb
Original file line number Diff line number Diff line change
@@ -1,109 +1,4 @@
require "logstash/devutils/rspec/spec_helper"
require "logstash/outputs/http"
require "logstash/codecs/plain"
require "thread"
require "sinatra"
require "webrick"
require "webrick/https"
require 'openssl'
require_relative "../supports/compressed_requests"

PORT = rand(65535-1024) + 1025

class LogStash::Outputs::Http
attr_writer :agent
attr_reader :request_tokens
end

# note that Sinatra startup and shutdown messages are directly logged to stderr so
# it is not really possible to disable them without reopening stderr which is not advisable.
#
# == Sinatra (v1.4.6) has taken the stage on 51572 for development with backup from WEBrick
# == Sinatra has ended his set (crowd applauds)
#
class TestApp < Sinatra::Base
# on the fly uncompress gzip content
use CompressedRequests

set :environment, :production
set :sessions, false

@@server_settings = {
:AccessLog => [], # disable WEBrick logging
:Logger => WEBrick::BasicLog::new(nil, WEBrick::BasicLog::FATAL)
}

def self.server_settings
@@server_settings
end

def self.server_settings=(settings)
@@server_settings = settings
end

def self.multiroute(methods, path, &block)
methods.each do |method|
method.to_sym
self.send method, path, &block
end
end

def self.last_request=(request)
@last_request = request
end

def self.last_request
@last_request
end

def self.retry_fail_count=(count)
@retry_fail_count = count
end

def self.retry_fail_count()
@retry_fail_count || 2
end

multiroute(%w(get post put patch delete), "/good") do
self.class.last_request = request
[200, "YUP"]
end

multiroute(%w(get post put patch delete), "/bad") do
self.class.last_request = request
[400, "YUP"]
end

multiroute(%w(get post put patch delete), "/retry") do
self.class.last_request = request

if self.class.retry_fail_count > 0
self.class.retry_fail_count -= 1
[429, "Will succeed in #{self.class.retry_fail_count}"]
else
[200, "Done Retrying"]
end
end
end

RSpec.configure do
#http://stackoverflow.com/questions/6557079/start-and-call-ruby-http-server-in-the-same-script
def start_app_and_wait(app, opts = {})
queue = Queue.new

Thread.start do
begin
app.start!({ server: 'WEBrick', port: PORT }.merge opts) do |server|
queue.push(server)
end
rescue => e
warn "Error starting app: #{e.inspect}" # ignore
end
end

queue.pop # blocks until the start! callback runs
end
end
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))

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

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

@@default_server_settings = TestApp.server_settings.dup

before do
cert, key = WEBrick::Utils.create_self_signed_cert 2048, [["CN", ssl_cert_host]], "Logstash testing"
TestApp.server_settings = @@default_server_settings.merge({
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
:SSLCertificate => cert,
:SSLPrivateKey => key
})
TestApp.server_settings = @@default_server_settings.merge(webrick_config)

TestApp.last_request = nil

@server = start_app_and_wait(TestApp)
end

let(:webrick_config) do
cert, key = WEBrick::Utils.create_self_signed_cert 2048, [["CN", ssl_cert_host]], "Logstash testing"
{
SSLEnable: true,
SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
SSLCertificate: cert,
SSLPrivateKey: key
}
end

after do
@server.shutdown # WEBrick::HTTPServer

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

end

context 'with supported_protocols set to (disabled) 1.1' do

let(:config) { super().merge 'ssl_supported_protocols' => ['TLSv1.1'], 'ssl_verification_mode' => 'none' }

it "keeps retrying due a protocol exception" do # TLSv1.1 not enabled by default
expect(subject).to receive(:log_failure).
with('Could not fetch URL', hash_including(message: 'No appropriate protocol (protocol is disabled or cipher suites are inappropriate)')).
at_least(:once)
Thread.start { subject.multi_receive [ event ] }
sleep 1.0
end

end unless tls_version_enabled_by_default?('TLSv1.1')

context 'with supported_protocols set to 1.2/1.3' do

let(:config) { super().merge 'ssl_supported_protocols' => ['TLSv1.2', 'TLSv1.3'], 'ssl_verification_mode' => 'none' }

let(:webrick_config) { super().merge SSLVersion: 'TLSv1.2' }

it "should process the request" do
subject.multi_receive [ event ]
expect(last_request_body).to include '"message":"hello!"'
end

end

context 'with supported_protocols set to 1.3' do

let(:config) { super().merge 'ssl_supported_protocols' => ['TLSv1.3'], 'ssl_verification_mode' => 'none' }

let(:webrick_config) { super().merge SSLVersion: 'TLSv1.3' }

it "should process the request" do
subject.multi_receive [ event ]
expect(last_request_body).to include '"message":"hello!"'
end

end if tls_version_enabled_by_default?('TLSv1.3') && JOpenSSL::VERSION > '0.12' # due WEBrick uses OpenSSL

end
136 changes: 136 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
require "logstash/devutils/rspec/spec_helper"
require "logstash/outputs/http"
require "logstash/codecs/plain"

require "thread"
require "sinatra"
require "webrick"
require "webrick/https"
require 'openssl'

require "supports/compressed_requests"

PORT = rand(65535-1024) + 1025

class LogStash::Outputs::Http
attr_writer :agent
attr_reader :request_tokens
end

# NOTE: extend WEBrick with support for config[:SSLVersion]
WEBrick::GenericServer.class_eval do
alias_method :__setup_ssl_context, :setup_ssl_context

def setup_ssl_context(config)
ctx = __setup_ssl_context(config)
ctx.ssl_version = config[:SSLVersion] if config[:SSLVersion]
ctx
end

end

# note that Sinatra startup and shutdown messages are directly logged to stderr so
# it is not really possible to disable them without reopening stderr which is not advisable.
#
# == Sinatra (v1.4.6) has taken the stage on 51572 for development with backup from WEBrick
# == Sinatra has ended his set (crowd applauds)
#
class TestApp < Sinatra::Base
# on the fly uncompress gzip content
use CompressedRequests

set :environment, :production
set :sessions, false

@@server_settings = {
:AccessLog => [], # disable WEBrick logging
:Logger => WEBrick::BasicLog::new(nil, WEBrick::BasicLog::FATAL)
}

def self.server_settings
@@server_settings
end

def self.server_settings=(settings)
@@server_settings = settings
end

def self.multiroute(methods, path, &block)
methods.each do |method|
method.to_sym
self.send method, path, &block
end
end

def self.last_request=(request)
@last_request = request
end

def self.last_request
@last_request
end

def self.retry_fail_count=(count)
@retry_fail_count = count
end

def self.retry_fail_count()
@retry_fail_count || 2
end

multiroute(%w(get post put patch delete), "/good") do
self.class.last_request = request
[200, "YUP"]
end

multiroute(%w(get post put patch delete), "/bad") do
self.class.last_request = request
[400, "YUP"]
end

multiroute(%w(get post put patch delete), "/retry") do
self.class.last_request = request

if self.class.retry_fail_count > 0
self.class.retry_fail_count -= 1
[429, "Will succeed in #{self.class.retry_fail_count}"]
else
[200, "Done Retrying"]
end
end
end

RSpec.configure do |config|
#http://stackoverflow.com/questions/6557079/start-and-call-ruby-http-server-in-the-same-script
def start_app_and_wait(app, opts = {})
queue = Queue.new

Thread.start do
begin
app.start!({ server: 'WEBrick', port: PORT }.merge opts) do |server|
yield(server) if block_given?
queue.push(server)
end
rescue => e
warn "Error starting app: #{e.inspect}" # ignore
end
end

queue.pop # blocks until the start! callback runs
end

config.extend(Module.new do

def tls_version_enabled_by_default?(tls_version)
begin
context = javax.net.ssl.SSLContext.getInstance('TLS')
context.init nil, nil, nil
context.getDefaultSSLParameters.getProtocols.include? tls_version.to_s
rescue => e
warn "#{__method__} failed : #{e.inspect}"
nil
end
end

end)
end

0 comments on commit 7d2b3f6

Please sign in to comment.