Skip to content

Commit 48ddb89

Browse files
authored
Feat: add ssl_supported_protocols option (#1055)
1 parent fe0a241 commit 48ddb89

File tree

12 files changed

+114
-19
lines changed

12 files changed

+114
-19
lines changed

.ci/Dockerfile.elasticsearch

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ ARG es_path=/usr/share/elasticsearch
66
ARG es_yml=$es_path/config/elasticsearch.yml
77
ARG SECURE_INTEGRATION
88
ARG ES_SSL_KEY_INVALID
9+
ARG ES_SSL_SUPPORTED_PROTOCOLS
910

1011
RUN rm -f $es_path/config/scripts
1112

@@ -25,6 +26,10 @@ RUN if [ "$SECURE_INTEGRATION" = "true" ] ; then \
2526
fi \
2627
fi
2728
RUN if [ "$SECURE_INTEGRATION" = "true" ] ; then echo "xpack.security.http.ssl.certificate_authorities: [ '$es_path/config/test_certs/ca.crt' ]" >> $es_yml; fi
29+
RUN if [ "$SECURE_INTEGRATION" = "true" ] && [ ! -z "$ES_SSL_SUPPORTED_PROTOCOLS" ] ; then echo "xpack.security.http.ssl.supported_protocols: ${ES_SSL_SUPPORTED_PROTOCOLS}" >> $es_yml; fi
2830

31+
RUN cat $es_yml
32+
33+
RUN if [ "$SECURE_INTEGRATION" = "true" ] ; then $es_path/bin/elasticsearch-users useradd admin -p elastic -r superuser; fi
2934
RUN if [ "$SECURE_INTEGRATION" = "true" ] ; then $es_path/bin/elasticsearch-users useradd simpleuser -p abc123 -r superuser; fi
3035
RUN if [ "$SECURE_INTEGRATION" = "true" ] ; then $es_path/bin/elasticsearch-users useradd 'f@ncyuser' -p 'ab%12#' -r superuser; fi

.ci/docker-compose.override.yml

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ services:
1212
- INTEGRATION=${INTEGRATION:-false}
1313
- SECURE_INTEGRATION=${SECURE_INTEGRATION:-false}
1414
- ES_SSL_KEY_INVALID=${ES_SSL_KEY_INVALID:-false}
15+
- ES_SSL_SUPPORTED_PROTOCOLS=$ES_SSL_SUPPORTED_PROTOCOLS
1516

1617
elasticsearch:
1718
build:
@@ -22,6 +23,9 @@ services:
2223
- INTEGRATION=${INTEGRATION:-false}
2324
- SECURE_INTEGRATION=${SECURE_INTEGRATION:-false}
2425
- ES_SSL_KEY_INVALID=${ES_SSL_KEY_INVALID:-false}
26+
- ES_SSL_SUPPORTED_PROTOCOLS=$ES_SSL_SUPPORTED_PROTOCOLS
27+
environment:
28+
- ES_JAVA_OPTS=-Xms640m -Xmx640m
2529
command: /usr/share/elasticsearch/elasticsearch-run.sh
2630
tty: true
2731
ports:

.ci/logstash-run.sh

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
#!/bin/bash
2+
3+
env
4+
25
set -ex
36

47
export PATH=$BUILD_DIR/gradle/bin:$PATH
58

69
if [[ "$SECURE_INTEGRATION" == "true" ]]; then
7-
ES_URL="https://elasticsearch:9200 -k"
10+
ES_URL="https://elasticsearch:9200"
811
else
912
ES_URL="http://elasticsearch:9200"
1013
fi
1114

15+
# CentOS 7 using curl defaults does not enable TLSv1.3
16+
CURL_OPTS="-k --tlsv1.2 --tls-max 1.3"
17+
1218
wait_for_es() {
1319
count=120
14-
while ! curl -s $ES_URL >/dev/null && [[ $count -ne 0 ]]; do
20+
while ! curl $CURL_OPTS $ES_URL >/dev/null && [[ $count -ne 0 ]]; do
1521
count=$(( $count - 1 ))
1622
[[ $count -eq 0 ]] && exit 1
1723
sleep 1
1824
done
19-
echo $(curl -s $ES_URL | python -c "import sys, json; print(json.load(sys.stdin)['version']['number'])")
25+
echo $(curl $CURL_OPTS -vi $ES_URL | python -c "import sys, json; print(json.load(sys.stdin)['version']['number'])")
2026
}
2127

2228
if [[ "$INTEGRATION" != "true" ]]; then
23-
bundle exec rspec -fd spec/unit -t ~integration -t ~secure_integration
29+
jruby -rbundler/setup -S rspec -fd spec/unit -t ~integration -t ~secure_integration
2430
else
2531

2632
if [[ "$SECURE_INTEGRATION" == "true" ]]; then
@@ -32,5 +38,5 @@ else
3238
echo "Waiting for elasticsearch to respond..."
3339
ES_VERSION=$(wait_for_es)
3440
echo "Elasticsearch $ES_VERSION is Up!"
35-
bundle exec rspec -fd $extra_tag_args --tag update_tests:painless --tag es_version:$ES_VERSION spec/integration
41+
jruby -rbundler/setup -S rspec -fd $extra_tag_args --tag update_tests:painless --tag es_version:$ES_VERSION spec/integration
3642
fi

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ env:
1212
- SECURE_INTEGRATION=true INTEGRATION=true ELASTIC_STACK_VERSION=8.x SNAPSHOT=true LOG_LEVEL=info
1313
- SECURE_INTEGRATION=true INTEGRATION=true ELASTIC_STACK_VERSION=7.x LOG_LEVEL=info
1414
- SECURE_INTEGRATION=true INTEGRATION=true ELASTIC_STACK_VERSION=7.x ES_SSL_KEY_INVALID=true LOG_LEVEL=info
15+
- SECURE_INTEGRATION=true INTEGRATION=true ELASTIC_STACK_VERSION=7.x ES_SSL_SUPPORTED_PROTOCOLS=TLSv1.3 LOG_LEVEL=info

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 11.5.0
2+
- Feat: add ssl_supported_protocols option [#1055](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1055)
3+
14
## 11.4.2
25
- [DOC] Add `v8` to supported values for ecs_compatiblity defaults [#1059](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1059)
36

docs/index.asciidoc

+20-2
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ This plugin supports the following configuration options plus the
355355
| <<plugins-{type}s-{plugin}-sniffing_path>> |<<string,string>>|No
356356
| <<plugins-{type}s-{plugin}-ssl>> |<<boolean,boolean>>|No
357357
| <<plugins-{type}s-{plugin}-ssl_certificate_verification>> |<<boolean,boolean>>|No
358+
| <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
358359
| <<plugins-{type}s-{plugin}-template>> |a valid filesystem path|No
359360
| <<plugins-{type}s-{plugin}-template_name>> |<<string,string>>|No
360361
| <<plugins-{type}s-{plugin}-template_overwrite>> |<<boolean,boolean>>|No
@@ -1004,6 +1005,23 @@ Option to validate the server's certificate. Disabling this severely compromises
10041005
For more information on disabling certificate verification please read
10051006
https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf
10061007

1008+
[id="plugins-{type}s-{plugin}-ssl_supported_protocols"]
1009+
===== `ssl_supported_protocols`
1010+
1011+
* Value type is <<string,string>>
1012+
* Allowed values are: `'TLSv1.1'`, `'TLSv1.2'`, `'TLSv1.3'`
1013+
* Default depends on the JDK being used. With up-to-date Logstash, the default is `['TLSv1.2', 'TLSv1.3']`.
1014+
`'TLSv1.1'` is not considered secure and is only provided for legacy applications.
1015+
1016+
List of allowed SSL/TLS versions to use when establishing a connection to the Elasticsearch cluster.
1017+
1018+
For Java 8 `'TLSv1.3'` is supported only since **8u262** (AdoptOpenJDK), but requires that you set the
1019+
`LS_JAVA_OPTS="-Djdk.tls.client.protocols=TLSv1.3"` system property in Logstash.
1020+
1021+
NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as the one packaged with Logstash,
1022+
the protocol is disabled by default and needs to be enabled manually by changing `jdk.tls.disabledAlgorithms` in
1023+
the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.1` needs to be removed from the list.
1024+
10071025
[id="plugins-{type}s-{plugin}-template"]
10081026
===== `template`
10091027

@@ -1018,8 +1036,8 @@ If not set, the included template will be used.
10181036

10191037
* Value type is <<string,string>>
10201038
* Default value depends on whether <<plugins-{type}s-{plugin}-ecs_compatibility>> is enabled:
1021-
** ECS Compatibility disabled: `logstash`
1022-
** ECS Compatibility enabled: `ecs-logstash`
1039+
** ECS Compatibility disabled: `logstash`
1040+
** ECS Compatibility enabled: `ecs-logstash`
10231041

10241042

10251043
This configuration option defines how the template is named inside Elasticsearch.

lib/logstash/outputs/elasticsearch/http_client.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,11 @@ def uris
283283
end
284284

285285
def client_settings
286-
@options[:client_settings] || {}
286+
@_client_settings ||= @options[:client_settings] || {}
287287
end
288288

289289
def ssl_options
290-
client_settings.fetch(:ssl, {})
290+
@_ssl_options ||= client_settings.fetch(:ssl, {})
291291
end
292292

293293
def http_compression

lib/logstash/outputs/elasticsearch/http_client_builder.rb

+5
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,16 @@ def self.setup_ssl(logger, params)
132132
ssl_options[:keystore] = keystore
133133
ssl_options[:keystore_password] = keystore_password.value if keystore_password
134134
end
135+
135136
if !params["ssl_certificate_verification"]
136137
logger.warn "You have enabled encryption but DISABLED certificate verification, " +
137138
"to make sure your data is secure remove `ssl_certificate_verification => false`"
138139
ssl_options[:verify] = :disable # false accepts self-signed but still validates hostname
139140
end
141+
142+
protocols = params['ssl_supported_protocols']
143+
ssl_options[:protocols] = protocols if protocols && protocols.any?
144+
140145
{ ssl: ssl_options }
141146
end
142147

lib/logstash/plugin_mixins/elasticsearch/api_configs.rb

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ module APIConfigs
6666
# Set the keystore password
6767
:keystore_password => { :validate => :password },
6868

69+
:ssl_supported_protocols => { :validate => ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], :default => [], :list => true },
70+
6971
# This setting asks Elasticsearch for the list of all cluster nodes and adds them to the hosts list.
7072
# Note: This will return ALL nodes with HTTP enabled (including master nodes!). If you use
7173
# this with master nodes, you probably want to disable HTTP on them by setting

logstash-output-elasticsearch.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Gem::Specification.new do |s|
22
s.name = 'logstash-output-elasticsearch'
3-
s.version = '11.4.2'
3+
s.version = '11.5.0'
44
s.licenses = ['apache-2.0']
55
s.summary = "Stores logs in Elasticsearch"
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"

spec/integration/outputs/index_spec.rb

+59-8
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,48 @@
6060

6161
let(:curl_opts) { nil }
6262

63+
let(:es_admin) { 'admin' } # default user added in ES -> 8.x requires auth credentials for /_refresh etc
64+
let(:es_admin_pass) { 'elastic' }
65+
6366
def curl_and_get_json_response(url, method: :get); require 'open3'
67+
cmd = "curl -s -v --show-error #{curl_opts} -X #{method.to_s.upcase} -k #{url}"
6468
begin
65-
stdout, status = Open3.capture2("curl #{curl_opts} -X #{method.to_s.upcase} -k #{url}")
69+
out, err, status = Open3.capture3(cmd)
6670
rescue Errno::ENOENT
6771
fail "curl not available, make sure curl binary is installed and available on $PATH"
6872
end
6973

7074
if status.success?
71-
LogStash::Json.load(stdout)
75+
http_status = err.match(/< HTTP\/1.1 (\d+)/)[1] || '0' # < HTTP/1.1 200 OK\r\n
76+
77+
if http_status.strip[0].to_i > 2
78+
error = (LogStash::Json.load(out)['error']) rescue nil
79+
if error
80+
fail "#{cmd.inspect} received an error: #{http_status}\n\n#{error.inspect}"
81+
else
82+
warn out
83+
fail "#{cmd.inspect} unexpected response: #{http_status}\n\n#{err}"
84+
end
85+
end
86+
87+
LogStash::Json.load(out)
7288
else
73-
fail "curl failed: #{status}\n #{stdout}"
89+
warn out
90+
fail "#{cmd.inspect} process failed: #{status}\n\n#{err}"
7491
end
7592
end
7693

94+
let(:initial_events) { [] }
95+
7796
before do
7897
subject.register
79-
subject.multi_receive([])
98+
subject.multi_receive(initial_events) if initial_events
8099
end
81-
100+
101+
after do
102+
subject.do_close
103+
end
104+
82105
shared_examples "an indexer" do |secure|
83106
it "ships events" do
84107
subject.multi_receive(events)
@@ -146,17 +169,17 @@ def curl_and_get_json_response(url, method: :get); require 'open3'
146169
let(:user) { "simpleuser" }
147170
let(:password) { "abc123" }
148171
let(:cacert) { "spec/fixtures/test_certs/ca.crt" }
149-
let(:es_url) {"https://elasticsearch:9200"}
172+
let(:es_url) { "https://#{get_host_port}" }
150173
let(:config) do
151174
{
152-
"hosts" => ["elasticsearch:9200"],
175+
"hosts" => [ get_host_port ],
153176
"user" => user,
154177
"password" => password,
155178
"ssl" => true,
156179
"cacert" => cacert,
157180
"index" => index
158181
}
159-
end
182+
end
160183

161184
let(:curl_opts) { "-u #{user}:#{password}" }
162185

@@ -197,6 +220,8 @@ def curl_and_get_json_response(url, method: :get); require 'open3'
197220

198221
else
199222

223+
let(:curl_opts) { "#{super()} --tlsv1.2 --tls-max 1.3 -u #{es_admin}:#{es_admin_pass}" } # due ES 8.x we need user/password
224+
200225
it_behaves_like("an indexer", true)
201226

202227
describe "with a password requiring escaping" do
@@ -219,6 +244,32 @@ def curl_and_get_json_response(url, method: :get); require 'open3'
219244
include_examples("an indexer", true)
220245
end
221246

247+
context 'with enforced TLSv1.3 protocol' do
248+
let(:config) { super().merge 'ssl_supported_protocols' => [ 'TLSv1.3' ] }
249+
250+
it_behaves_like("an indexer", true)
251+
end
252+
253+
context 'with enforced TLSv1.2 protocol (while ES only enabled TLSv1.3)' do
254+
let(:config) { super().merge 'ssl_supported_protocols' => [ 'TLSv1.2' ] }
255+
256+
let(:initial_events) { nil }
257+
258+
it "does not ship events" do
259+
curl_and_get_json_response index_url, method: :put # make sure index exists
260+
Thread.start { subject.multi_receive(events) } # we'll be stuck in a retry loop
261+
sleep 2.5
262+
263+
curl_and_get_json_response "#{es_url}/_refresh", method: :post
264+
265+
result = curl_and_get_json_response "#{index_url}/_count?q=*"
266+
cur_count = result["count"]
267+
expect(cur_count).to eq(0) # ES output keeps re-trying but ends up with a
268+
# [Manticore::ClientProtocolException] Received fatal alert: protocol_version
269+
end
270+
271+
end if ENV['ES_SSL_SUPPORTED_PROTOCOLS'] == 'TLSv1.3'
272+
222273
end
223274

224275
end

spec/unit/outputs/elasticsearch_ssl_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
it "should pass the flag to the ES client" do
3535
expect(::Manticore::Client).to receive(:new) do |args|
36-
expect(args[:ssl]).to eq(:enabled => true, :verify => :disable)
36+
expect(args[:ssl]).to match hash_including(:enabled => true, :verify => :disable)
3737
end.and_return(manticore_double)
3838

3939
subject.register

0 commit comments

Comments
 (0)