Skip to content

Commit 19355a2

Browse files
committed
implement proper hostname verification
with ruby < 2.4 support see ruby/openssl#60
1 parent 57dfc7e commit 19355a2

File tree

3 files changed

+96
-14
lines changed

3 files changed

+96
-14
lines changed

README.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,13 @@ You can also enable SSL without a certificate:
182182
LogStashLogger.new(type: :tcp, port: 5228, ssl_enable: true)
183183
```
184184

185-
You can also specify an SSL context. This can be used to disable host verification.
185+
Specify an SSL context to have more control over the behavior. For example,
186+
set the verify mode:
186187

187188
```ruby
188189
ctx = OpenSSL::SSL::SSLContext.new
189190
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
190-
LogStashLogger.new(type: :tcp, port: 5228, ssl_enable: true, ssl_context: ctx)
191+
LogStashLogger.new(type: :tcp, port: 5228, ssl_context: ctx)
191192
```
192193

193194
The following Logstash configuration is required for SSL:
@@ -205,6 +206,45 @@ input {
205206
}
206207
```
207208

209+
### Hostname Validation
210+
211+
Ruby 2.4.0 will validate the hostname upon connection when configuring the SSL
212+
context with `:verify_mode`, `:verify_hostname` and
213+
`OpenSSL::SSL::SSLSocket#hostname=`.
214+
215+
LogStashLogger will set `OpenSSL::SSL::SSLSocket#hostname` to the value of
216+
`:verify_hostname`.
217+
218+
219+
220+
```ruby
221+
ctx_params = {
222+
cert: '/path/to/cert.pem',
223+
verify_mode: OpenSSL::SSL::VERIFY_PEER,
224+
verify_hostname: true
225+
}
226+
ctx = OpenSSL::SSL::SSLContext.new
227+
ctx.set_params(ctx_params)
228+
229+
LogStashLogger.new(type: :tcp, port: 5228, ssl_context: ctx, verify_hostname: 'www.example.com')
230+
```
231+
232+
To have LogStashLogger perform hostname validation with older Ruby versions:
233+
234+
```ruby
235+
ctx_params = {
236+
cert: '/path/to/cert.pem',
237+
verify_mode: OpenSSL::SSL::VERIFY_PEER
238+
}
239+
ctx = OpenSSL::SSL::SSLContext.new
240+
ctx.set_params(ctx_params)
241+
242+
LogStashLogger.new(type: :tcp, port: 5228, ssl_context: ctx, verify_hostname: 'www.example.com')
243+
```
244+
245+
If you supply a falsey value to `:verify_hostname`, hostname validation will be
246+
skipped.
247+
208248
## Custom Log Fields
209249

210250
`LogStashLogger` by default will log a JSON object with the format below.

lib/logstash-logger/device/tcp.rb

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def initialize(opts)
1212
@ssl_context = opts.fetch(:ssl_context, nil)
1313
@use_ssl = !!(@ssl_certificate || opts[:ssl_context])
1414
@use_ssl = opts[:ssl_enable] if opts.has_key? :ssl_enable
15+
@verify_hostname = opts.fetch(:verify_hostname, true)
1516
end
1617

1718
def ssl_context
@@ -44,10 +45,10 @@ def non_ssl_connect
4445
def ssl_connect
4546
non_ssl_connect
4647
@io = OpenSSL::SSL::SSLSocket.new(@io, ssl_context)
48+
# support ruby 2.4.0+ hostname validation
49+
@io.hostname = hostname if @io.respond_to?(:hostname=)
4750
@io.connect
48-
if ssl_context && ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
49-
@io.post_connection_check(@host)
50-
end
51+
verify_hostname!
5152
end
5253

5354
def certificate_context
@@ -56,6 +57,23 @@ def certificate_context
5657
ctx.set_params(cert: @ssl_certificate)
5758
end
5859
end
60+
61+
def verify_hostname?
62+
return false unless ssl_context
63+
!! @verify_hostname
64+
end
65+
66+
def verify_hostname!
67+
@io.post_connection_check(hostname) if verify_hostname?
68+
end
69+
70+
def hostname
71+
if String === @verify_hostname
72+
@verify_hostname
73+
else
74+
@host
75+
end
76+
end
5977
end
6078
end
6179
end

spec/device/tcp_spec.rb

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,45 @@
3737
expect(ssl_tcp_device.use_ssl?).to be_truthy
3838
end
3939

40-
context 'with a provided SSL context' do
40+
context 'hostname validation' do
4141
let(:ssl_context) { double('test_ssl_context', verify_mode: OpenSSL::SSL::VERIFY_PEER) }
42-
let(:ssl_tcp_device) { LogStashLogger::Device.new(type: :tcp, port: port, sync: true, ssl_context: ssl_context) }
42+
let(:ssl_tcp_options) { { type: :tcp, port: port, sync: true, ssl_context: ssl_context } }
4343

44-
it "checks ssl certificate validity" do
45-
expect(ssl_socket).to receive(:post_connection_check).with(HOST)
46-
ssl_tcp_device.connect
44+
context 'is enabled by default' do
45+
let(:ssl_tcp_device) { LogStashLogger::Device.new(ssl_tcp_options) }
46+
47+
it 'validates' do
48+
expect(ssl_tcp_device.send(:verify_hostname?)).to be_truthy
49+
expect(ssl_socket).to receive(:post_connection_check).with HOST
50+
ssl_tcp_device.connect
51+
end
4752
end
4853

49-
it "does not check host validity of certificate" do
50-
expect(ssl_context).to receive(:verify_mode) { OpenSSL::SSL::VERIFY_NONE }
51-
expect(ssl_socket).not_to receive(:post_connection_check).with(HOST)
52-
ssl_tcp_device.connect
54+
context 'is disabled explicitly' do
55+
let(:ssl_tcp_device) { LogStashLogger::Device.new(ssl_tcp_options.merge(verify_hostname: false)) }
56+
57+
it 'does not validate' do
58+
expect(ssl_tcp_device.send(:verify_hostname?)).to be_falsey
59+
expect(ssl_socket).not_to receive(:post_connection_check)
60+
ssl_tcp_device.connect
61+
end
5362
end
5463

64+
context 'is implicitly enabled by providing a hostname' do
65+
let(:hostname) { 'www.example.com' }
66+
let(:ssl_tcp_device) { LogStashLogger::Device.new(ssl_tcp_options.merge(verify_hostname: hostname)) }
67+
68+
it 'validates with supplied hostname' do
69+
expect(ssl_socket).to receive(:post_connection_check).with hostname
70+
ssl_tcp_device.connect
71+
end
72+
end
73+
end
74+
75+
context 'with a provided SSL context' do
76+
let(:ssl_context) { double('test_ssl_context', verify_mode: OpenSSL::SSL::VERIFY_PEER) }
77+
let(:ssl_tcp_device) { LogStashLogger::Device.new(type: :tcp, port: port, sync: true, ssl_context: ssl_context) }
78+
5579
it 'creates the socket using that context' do
5680
expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_context)
5781
ssl_tcp_device.connect

0 commit comments

Comments
 (0)