Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: improved error handling/logging/unwraping #133

Merged
merged 5 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 3.3.7
- Feat: improved error handling/logging/unwraping [#133](https://github.com/logstash-plugins/logstash-input-http/pull/133)

## 3.3.6
- Fixes a regression introduced in 3.1.0's migration to the Netty back-end that broke some users'
browser-based workflows. When an instance of this plugin that is configured to require Basic
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.6
3.3.7
47 changes: 35 additions & 12 deletions lib/logstash/inputs/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,30 +217,24 @@ def create_http_server(message_handler)
def build_ssl_params
return nil unless @ssl

ssl_builder = nil

if @keystore && @keystore_password
ssl_builder = org.logstash.plugins.inputs.http.util.JksSslBuilder.new(@keystore, @keystore_password.value)
else
begin
ssl_builder = org.logstash.plugins.inputs.http.util.SslSimpleBuilder.new(@ssl_certificate, @ssl_key, @ssl_key_passphrase.nil? ? nil : @ssl_key_passphrase.value)
.setCipherSuites(normalized_ciphers)
ssl_builder = org.logstash.plugins.inputs.http.util.SslSimpleBuilder
.new(@ssl_certificate, @ssl_key, @ssl_key_passphrase.nil? ? nil : @ssl_key_passphrase.value)
.setCipherSuites(normalized_ciphers)
rescue java.lang.IllegalArgumentException => e
raise LogStash::ConfigurationError.new(e)
@logger.error("SSL configuration invalid", error_details(e))
raise LogStash::ConfigurationError, e
end

if client_authentication?
ssl_builder.setCertificateAuthorities(@ssl_certificate_authorities)
end
end

ssl_context = ssl_builder.build()
ssl_handler_provider = org.logstash.plugins.inputs.http.util.SslHandlerProvider.new(ssl_context)
ssl_handler_provider.setVerifyMode(@ssl_verify_mode_final.upcase)
ssl_handler_provider.setProtocols(convert_protocols)
ssl_handler_provider.setHandshakeTimeoutMilliseconds(@ssl_handshake_timeout)

ssl_handler_provider
new_ssl_handshake_provider(ssl_builder)
end

def ssl_key_configured?
Expand All @@ -259,6 +253,8 @@ def require_certificate_authorities?
@ssl_verify_mode_final == "force_peer" || @ssl_verify_mode_final == "peer"
end

private

def normalized_ciphers
@cipher_suites.map(&:upcase)
end
Expand All @@ -267,4 +263,31 @@ def convert_protocols
TLS.get_supported(@tls_min_version..@tls_max_version).map(&:name)
end

def new_ssl_handshake_provider(ssl_builder)
begin
ssl_handler_provider = org.logstash.plugins.inputs.http.util.SslHandlerProvider.new(ssl_builder.build())
ssl_handler_provider.setVerifyMode(@ssl_verify_mode_final.upcase)
ssl_handler_provider.setProtocols(convert_protocols)
ssl_handler_provider.setHandshakeTimeoutMilliseconds(@ssl_handshake_timeout)
ssl_handler_provider
rescue java.lang.IllegalArgumentException => e
@logger.error("SSL configuration invalid", error_details(e))
raise LogStash::ConfigurationError, e
rescue java.lang.Exception => e
@logger.error("SSL configuration failed", error_details(e, true))
raise e
end
end

def error_details(e, trace = false)
error_details = { :exception => e.class, :message => e.message }
error_details[:backtrace] = e.backtrace if trace || @logger.debug?
cause = e.cause
if cause && e != cause
error_details[:cause] = { :exception => cause.class, :message => cause.message }
error_details[:cause][:backtrace] = cause.backtrace if trace || @logger.debug?
end
error_details
end

end # class LogStash::Inputs::Http
84 changes: 71 additions & 13 deletions spec/inputs/http_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -386,21 +386,21 @@
let(:ssl_certificate) { ssc.certificate }
let(:ssl_key) { ssc.private_key }

let(:config) do
{ "port" => port, "ssl" => true, "ssl_certificate" => ssl_certificate.path, "ssl_key" => ssl_key.path }
end

after(:each) { ssc.delete }

subject { LogStash::Inputs::Http.new("port" => port, "ssl" => true,
"ssl_certificate" => ssl_certificate.path,
"ssl_key" => ssl_key.path) }
subject { LogStash::Inputs::Http.new(config) }

it "should not raise exception" do
expect { subject.register }.to_not raise_exception
end

context "with ssl_verify_mode = none" do
subject { LogStash::Inputs::Http.new("port" => port, "ssl" => true,
"ssl_certificate" => ssl_certificate.path,
"ssl_key" => ssl_key.path,
"ssl_verify_mode" => "none"
) }
subject { LogStash::Inputs::Http.new(config.merge("ssl_verify_mode" => "none")) }

it "should not raise exception" do
expect { subject.register }.to_not raise_exception
end
Expand All @@ -419,11 +419,8 @@
end
end
context "with verify_mode = none" do
subject { LogStash::Inputs::Http.new("port" => port, "ssl" => true,
"ssl_certificate" => ssl_certificate.path,
"ssl_key" => ssl_key.path,
"verify_mode" => "none"
) }
subject { LogStash::Inputs::Http.new(config.merge("verify_mode" => "none")) }

it "should not raise exception" do
expect { subject.register }.to_not raise_exception
end
Expand All @@ -441,6 +438,67 @@
end
end
end

context "with invalid cipher_suites" do
let(:config) { super.merge("cipher_suites" => "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38") }

it "should raise a configuration error" do
expect( subject.logger ).to receive(:error) do |msg, opts|
expect( msg ).to match /.*?configuration invalid/
expect( opts[:message] ).to match /TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38.*? not available/
end
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
end
end

context "with invalid ssl certificate" do
before do
cert = File.readlines path = config["ssl_certificate"]
i = cert.index { |line| line.index('END CERTIFICATE') }
cert[i - 1] = ''
File.write path, cert.join("\n")
end

it "should raise a configuration error" do
expect( subject.logger ).to receive(:error) do |msg, opts|
expect( msg ).to match /SSL configuration invalid/
expect( opts[:message] ).to match /File does not contain valid certificate/i
end
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
end
end

context "with invalid ssl key config" do
let(:config) { super.merge("ssl_key_passphrase" => "1234567890") }

it "should raise a configuration error" do
expect( subject.logger ).to receive(:error) do |msg, opts|
expect( msg ).to match /SSL configuration invalid/
expect( opts[:message] ).to match /File does not contain valid private key/i
end
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
end
end

context "with invalid ssl certificate_authorities" do
let(:config) do
super.merge("ssl_verify_mode" => "peer",
"ssl_certificate_authorities" => [ ssc.certificate.path, ssc.private_key.path ])
end

it "should raise a cert error" do
expect( subject.logger ).to receive(:error) do |msg, opts|
expect( msg ).to match(/SSL configuration failed/), lambda { "unexpected: logger.error #{msg.inspect}, #{opts.inspect}" }
expect( opts[:message] ).to match /signed fields invalid/
end
begin
subject.register
rescue Java::JavaSecurityCert::CertificateParsingException
:pass
end
end
end

end
end
end
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
package org.logstash.plugins.inputs.http.util;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;


import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;

import io.netty.handler.ssl.SslContext;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

public class JksSslBuilder implements SslBuilder {
private static final String ALGORITHM_SUN_X509 = "SunX509";
Expand All @@ -28,7 +20,7 @@ public JksSslBuilder(String keyStorePath, String keyStorePassword) {
this.keyStorePassword = keyStorePassword.toCharArray();
}

public SslContext build() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
public SslContext build() throws Exception {
String algorithm = Security.getProperty(ALGORITHM);
if (algorithm == null) {
algorithm = ALGORITHM_SUN_X509;
Expand All @@ -47,6 +39,6 @@ public SslContext build() throws IOException, KeyStoreException, CertificateExce
SslContextBuilder builder = SslContextBuilder.forServer(kmf);
builder.trustManager(tmf);

return builder.build();
return SslSimpleBuilder.doBuild(builder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

public interface SslBuilder {

SslContext build() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException;
/**
* @return context
* @throws Exception (IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException)
*/
SslContext build() throws Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Arrays;
import java.util.List;
import javax.crypto.Cipher;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocketFactory;

public class SslSimpleBuilder implements SslBuilder {
Expand Down Expand Up @@ -85,7 +86,7 @@ public SslSimpleBuilder setCertificateAuthorities(String[] cert) {
return this;
}

public SslContext build() throws IOException, NoSuchAlgorithmException, CertificateException {
public SslContext build() throws Exception {
SslContextBuilder builder = SslContextBuilder.forServer(sslCertificateFile, sslKeyFile, passPhrase);

if(logger.isDebugEnabled()) {
Expand All @@ -102,7 +103,26 @@ public SslContext build() throws IOException, NoSuchAlgorithmException, Certific
builder.trustManager(loadCertificateCollection(certificateAuthorities));
}

return builder.build();
return doBuild(builder);
}

// NOTE: copy-pasta from input-beats
static SslContext doBuild(final SslContextBuilder builder) throws Exception {
try {
return builder.build();
} catch (SSLException e) {
logger.debug("Failed to initialize SSL", e);
// unwrap generic wrapped exception from Netty's JdkSsl{Client|Server}Context
if ("failed to initialize the server-side SSL context".equals(e.getMessage()) ||
"failed to initialize the client-side SSL context".equals(e.getMessage())) {
// Netty catches Exception and simply wraps: throw new SSLException("...", e);
if (e.getCause() instanceof Exception) throw (Exception) e.getCause();
}
throw e;
} catch (Exception e) {
logger.debug("Failed to initialize SSL", e);
throw e;
}
}

private X509Certificate[] loadCertificateCollection(String[] certificates) throws IOException, CertificateException {
Expand Down