Skip to content

Add client_chain to send intermediate certificates with excon - #201

Open
lbarrosby wants to merge 2 commits into
fluent:mainfrom
lbarrosby:client-chain
Open

lbarrosby wants to merge 2 commits into
fluent:mainfrom
lbarrosby:client-chain

Conversation

@lbarrosby

Copy link
Copy Markdown

Fixes #139.

With the default excon backend, only the first certificate of client_cert is sent. When the client certificate is issued by an intermediate CA and the server trusts only the root, the handshake fails with unknown ca (PKIX path building failed on the OpenSearch side). excon sends extra certificates only from its own client_chain option, which the plugin did not expose.

As proposed in the issue, this adds a client_chain parameter (path to the intermediate certificates) to out_opensearch, which out_opensearch_data_stream inherits, and to in_opensearch, and passes it to excon. typhoeus already sends the whole chain from client_cert, so it is left as is and the README says so.

Tests:

  • configure client_chain for excon (out) and test_configure_client_chain_for_excon (in) check the parameter and the excon backend_options. Both fail on main (undefined method 'client_chain').
  • Default configuration tests now also check that client_chain is nil.
  • Checked end to end with a WEBrick server that requires a client certificate and trusts only a root CA, using the plugin's own client and a client certificate issued by an intermediate CA: with client_cert alone client.info fails with tlsv1 alert unknown ca; with client_chain pointing at the intermediate it succeeds.
  • test_out_opensearch.rb (311 tests) and test_in_opensearch.rb pass locally on Ruby 4.0.

(check all that apply)

  • tests added
  • tests passing
  • README updated (if needed)
  • README Table of Contents updated (if needed)
  • History.md and version in gemspec are untouched
  • backward compatible

The default excon backend reads only the first certificate of
client_cert, so a client certificate issued by an intermediate CA that
the server does not know fails the handshake with "unknown ca". excon
sends extra certificates only from its separate client_chain option,
which the plugin did not expose.

Add a client_chain parameter to out_opensearch (and so to
out_opensearch_data_stream) and in_opensearch, and pass it to excon.

Signed-off-by: Lucas Barros <lucasbarros.kam@gmail.com>

@Watson1978 Watson1978 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this.

I checked the fix with a local TLS server. The server requires a client certificate and trusts only the root CA. The client certificate is issued by an intermediate CA. On main, both out_opensearch and in_opensearch fail with unknown ca. With this PR, both succeed when client_chain points at the intermediate certificate. The existing tests pass too.

For in_opensearch, I used check_connection false in this test. With the default check_connection true, the connection check does not send the client certificate, so it fails on main too. That is a separate issue, and this PR does not need to fix it.

One change is needed before merging. When client_chain is not set, the excon options should stay the same as on main. Please see the inline comments.

Comment thread lib/fluent/plugin/out_opensearch.rb Outdated
case @http_backend
when :excon
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass, nonblock: @http_backend_excon_nonblock }
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass, nonblock: @http_backend_excon_nonblock, client_chain: @client_chain }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MUST: When client_chain is not set, this passes client_chain: nil to excon. excon starts from Excon.defaults and then merges the connection options. So the explicit nil hides Excon.defaults[:client_chain].

A setup that sets Excon.defaults[:client_chain] and does not set client_chain in the plugin works on main. With this PR it fails with unknown ca. I reproduced this with a local TLS server for both out_opensearch and in_opensearch, using check_connection false for in_opensearch.

Please add the key only when client_chain is set:

Suggested change
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass, nonblock: @http_backend_excon_nonblock, client_chain: @client_chain }
options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass, nonblock: @http_backend_excon_nonblock }
options[:client_chain] = @client_chain if @client_chain
options

Please do not use .compact here. It would also drop client_key: nil and client_cert: nil, and that changes how Excon.defaults applies to them.

Could you also add this line to test_configure, right after assert_nil instance.client_chain? Please add it in both test/plugin/test_out_opensearch.rb and test/plugin/test_in_opensearch.rb:

assert_false instance.backend_options.key?(:client_chain)

Both assertions fail on the current PR head and pass after applying both suggestions in this review.

Comment thread lib/fluent/plugin/in_opensearch.rb Outdated
case @http_backend
when :excon
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass, client_chain: @client_chain }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MUST: Same issue as in out_opensearch.rb. Please add client_chain only when it is set:

Suggested change
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass, client_chain: @client_chain }
options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
options[:client_chain] = @client_chain if @client_chain
options

An explicit client_chain: nil hides Excon.defaults[:client_chain], so a
setup that sets it there and not in the plugin would stop sending the
intermediate certificates. Leave the other options as they are.

Signed-off-by: Lucas Barros <lucasbarros.kam@gmail.com>
@lbarrosby

Copy link
Copy Markdown
Author

Good catch on Excon.defaults. client_chain is now added only when it's set, in both out_opensearch and in_opensearch, without .compact, and test_configure checks backend_options.key?(:client_chain) in both test files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fluentd opensearch plugin does not send full certificate chain

2 participants