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

Fix SSL Java Keystore and Truststore support #171

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.8.0
- Fixed SSL Java KeyStore support [#171](https://github.com/logstash-plugins/logstash-input-http/pull/171)
- Added `ssl_keystore_type` configuration
- Added SSL Java TrustStore configurations (`ssl_truststore_type`, `ssl_truststore_path` and `ssl_truststore_password`)

## 3.7.3
- bump netty to 4.1.100 [#170](https://github.com/logstash-plugins/logstash-input-http/pull/170)

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.7.3
3.8.0
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation "org.apache.logging.log4j:log4j-core:${log4jVersion}"
testImplementation 'org.elasticsearch:securemock:1.2'

implementation "io.netty:netty-buffer:${nettyVersion}"
implementation "io.netty:netty-codec:${nettyVersion}"
Expand Down
43 changes: 42 additions & 1 deletion docs/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ This plugin supports the following configuration options plus the <<plugins-{typ
| <<plugins-{type}s-{plugin}-ssl_key_passphrase>> |<<password,password>>|No
| <<plugins-{type}s-{plugin}-ssl_keystore_password>> |<<password,password>>|No
| <<plugins-{type}s-{plugin}-ssl_keystore_path>> |<<path,path>>|No
| <<plugins-{type}s-{plugin}-ssl_keystore_type>> |<<string,string>>|No
| <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<array,array>>|No
| <<plugins-{type}s-{plugin}-ssl_truststore_password>> |<<password,password>>|No
| <<plugins-{type}s-{plugin}-ssl_truststore_path>> |<<path,path>>|No
| <<plugins-{type}s-{plugin}-ssl_truststore_type>> |<<string,string>>|No
| <<plugins-{type}s-{plugin}-ssl_verify_mode>> |<<string,string>>, one of `["none", "peer", "force_peer"]`|__Deprecated__
| <<plugins-{type}s-{plugin}-threads>> |<<number,number>>|No
| <<plugins-{type}s-{plugin}-tls_max_version>> |<<number,number>>|__Deprecated__
Expand Down Expand Up @@ -405,7 +409,18 @@ SSL key passphrase to use.
* Value type is <<path,path>>
* There is no default value for this setting.

The JKS keystore to validate the client's certificates
The path for the keystore file that contains a private key and certificate.
It must be either a Java keystore (jks) or a PKCS#12 file.

NOTE: You cannot use this setting and <<plugins-{type}s-{plugin}-ssl_certificate>> at the same time.

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

* Value can be any of: `jks`, `pkcs12`
* If not provided, the value will be inferred from the keystore filename.

The format of the keystore file. It must be either `jks` or `pkcs12`.

[id="plugins-{type}s-{plugin}-ssl_keystore_password"]
===== `ssl_keystore_password`
Expand All @@ -432,6 +447,32 @@ NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as
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_truststore_password"]
===== `ssl_truststore_password`

* Value type is <<password,password>>
* There is no default value for this setting.

Set the truststore password

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

* Value type is <<path,path>>
* There is no default value for this setting.

The path for the keystore that contains the certificates to trust. It must be either a Java keystore (jks) or a PKCS#12 file.

NOTE: You cannot use this setting and <<plugins-{type}s-{plugin}-ssl_certificate_authorities>> at the same time.

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

* Value can be any of: `jks`, `pkcs12`
* If not provided, the value will be inferred from the truststore filename.

The format of the truststore file. It must be either `jks` or `pkcs12`.

[id="plugins-{type}s-{plugin}-ssl_verify_mode"]
===== `ssl_verify_mode`
deprecated[3.7.0, Replaced by <<plugins-{type}s-{plugin}-ssl_client_authentication>>]
Expand Down
104 changes: 66 additions & 38 deletions lib/logstash/inputs/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,24 @@ class LogStash::Inputs::Http < LogStash::Inputs::Base
# The JKS keystore password
config :ssl_keystore_password, :validate => :password

# The JKS keystore to validate the client's certificates
# The path for the keystore file that contains a private key and certificate
config :ssl_keystore_path, :validate => :path

# The format of the keystore file. It must be either jks or pkcs12
config :ssl_keystore_type, :validate => %w[pkcs12 jks]

# SSL key passphrase to use.
config :ssl_key_passphrase, :validate => :password

# Set the truststore password
config :ssl_truststore_password, :validate => :password

# The path for the keystore that contains the certificates to trust. It must be either a Java keystore (jks) or a PKCS#12 file
config :ssl_truststore_path, :validate => :path

# The format of the truststore file. It must be either jks or pkcs12
config :ssl_truststore_type, :validate => %w[pkcs12 jks]

# Validate client certificates against these authorities.
# You can define multiple files or paths. All the certificates will
# be read and added to the trust store. You need to configure the `ssl_client_authentication`
Expand Down Expand Up @@ -301,18 +313,31 @@ def validate_ssl_settings!
raise LogStash::ConfigurationError, 'An `ssl_certificate` is required when using an `ssl_key`'
end

unless ssl_key_configured? || ssl_jks_configured?
unless ssl_certificate_configured? || ssl_keystore_configured?
raise LogStash::ConfigurationError, "Either an `ssl_certificate` or `ssl_keystore_path` is required when SSL is enabled `#{ssl_config_name} => true`"
end

if require_certificate_authorities? && !certificate_authorities_configured?
config_name, optional, required = provided_client_authentication_config([SSL_CLIENT_AUTH_OPTIONAL, SSL_CLIENT_AUTH_REQUIRED])
raise LogStash::ConfigurationError, "Using `#{config_name}` set to `#{optional}` or `#{required}`, requires the configuration of `ssl_certificate_authorities`"
if ssl_certificate_configured? && ssl_keystore_configured?
raise LogStash::ConfigurationError, 'Use either an `ssl_certificate` or an `ssl_keystore_path`'
end

if !require_certificate_authorities? && certificate_authorities_configured?
config_name, optional, required = provided_client_authentication_config([SSL_CLIENT_AUTH_OPTIONAL, SSL_CLIENT_AUTH_REQUIRED])
raise LogStash::ConfigurationError, "The configuration of `ssl_certificate_authorities` requires setting `#{config_name}` to `#{optional}` or '#{required}'"
if ssl_certificate_authorities_configured? && ssl_truststore_configured?
raise LogStash::ConfigurationError, 'Use either an `ssl_certificate_authorities` or an `ssl_truststore_path`'
end

cli_auth_config_name, cli_auth_optional_val, cli_auth_required_val = provided_ssl_client_authentication_config([SSL_CLIENT_AUTH_OPTIONAL, SSL_CLIENT_AUTH_REQUIRED])
if ssl_client_authentication_enabled?
# Ensure any CA is configured. By default, the keystore can also be used as CA
unless ssl_certificate_authorities_configured? || ssl_truststore_configured? || ssl_keystore_configured?
raise LogStash::ConfigurationError, "Using `#{cli_auth_config_name}` set to `#{cli_auth_optional_val}` or `#{cli_auth_required_val}`, requires the configuration of `ssl_certificate_authorities` or `ssl_truststore_path`"
end
else
if ssl_truststore_configured?
raise LogStash::ConfigurationError, "The configuration of `ssl_truststore_path` requires setting `#{cli_auth_config_name}` to `#{cli_auth_optional_val}` or '#{cli_auth_required_val}'"
end
if ssl_certificate_authorities_configured?
raise LogStash::ConfigurationError, "The configuration of `ssl_certificate_authorities` requires setting `#{cli_auth_config_name}` to `#{cli_auth_optional_val}` or '#{cli_auth_required_val}'"
end
end
end

Expand Down Expand Up @@ -372,73 +397,76 @@ def create_http_server(message_handler)
def build_ssl_params
return nil unless @ssl_enabled

if @ssl_keystore_path && @ssl_keystore_password
ssl_builder = org.logstash.plugins.inputs.http.util.JksSslBuilder.new(@ssl_keystore_path, @ssl_keystore_password.value)
else
ssl_builder = new_ssl_simple_builder
end

new_ssl_handshake_provider(ssl_builder)
new_ssl_handshake_provider(new_ssl_simple_builder)
end

def new_ssl_simple_builder
passphrase = @ssl_key_passphrase.nil? ? nil : @ssl_key_passphrase.value
begin
ssl_context_builder = SslSimpleBuilder.new(@ssl_certificate, @ssl_key, passphrase)
.setProtocols(@ssl_supported_protocols)
.setCipherSuites(normalized_cipher_suites)
if ssl_keystore_configured?
ssl_context_builder = SslSimpleBuilder.withKeyStore(@ssl_keystore_type, @ssl_keystore_path, @ssl_keystore_password&.value)
else
ssl_context_builder = SslSimpleBuilder.withPemCertificate(@ssl_certificate, @ssl_key, @ssl_key_passphrase&.value)
end

if client_authentication_enabled?
ssl_context_builder.setClientAuthentication(ssl_simple_builder_verify_mode, @ssl_certificate_authorities)
ssl_context_builder.setProtocols(@ssl_supported_protocols)
.setCipherSuites(normalized_cipher_suites)
.setClientAuthentication(ssl_simple_builder_verify_mode)

if ssl_client_authentication_enabled?
if ssl_certificate_authorities_configured?
ssl_context_builder.setCertificateAuthorities(@ssl_certificate_authorities)
elsif ssl_truststore_configured?
ssl_context_builder.setTrustStore(@ssl_truststore_type, @ssl_truststore_path, @ssl_truststore_password&.value)
end
end

ssl_context_builder
rescue java.lang.IllegalArgumentException => e
rescue => e
@logger.error("SSL configuration invalid", error_details(e))
raise LogStash::ConfigurationError, e
end
end

def ssl_simple_builder_verify_mode
return SslSimpleBuilder::SslClientVerifyMode::OPTIONAL if client_authentication_optional?
return SslSimpleBuilder::SslClientVerifyMode::REQUIRED if client_authentication_required?
return SslSimpleBuilder::SslClientVerifyMode::NONE if client_authentication_none?
return SslSimpleBuilder::SslClientVerifyMode::OPTIONAL if ssl_client_authentication_optional?
return SslSimpleBuilder::SslClientVerifyMode::REQUIRED if ssl_client_authentication_required?
return SslSimpleBuilder::SslClientVerifyMode::NONE if ssl_client_authentication_none?
raise LogStash::ConfigurationError, "Invalid `ssl_client_authentication` value #{@ssl_client_authentication}"
end

def ssl_key_configured?
!!(@ssl_certificate && @ssl_key)
def ssl_certificate_configured?
!(@ssl_certificate.nil? || @ssl_certificate.empty?)
end

def ssl_jks_configured?
!!(@ssl_keystore_path && @ssl_keystore_password)
def ssl_keystore_configured?
!(@ssl_keystore_path.nil? || @ssl_keystore_path.empty?)
end

def client_authentication_enabled?
client_authentication_optional? || client_authentication_required?
def ssl_truststore_configured?
!(@ssl_truststore_path.nil? || @ssl_truststore_path.empty?)
end

def require_certificate_authorities?
client_authentication_required? || client_authentication_optional?
def ssl_client_authentication_enabled?
ssl_client_authentication_optional? || ssl_client_authentication_required?
end

def certificate_authorities_configured?
def ssl_certificate_authorities_configured?
@ssl_certificate_authorities && @ssl_certificate_authorities.size > 0
end

def client_authentication_required?
def ssl_client_authentication_required?
@ssl_client_authentication && @ssl_client_authentication.downcase == SSL_CLIENT_AUTH_REQUIRED
end

def client_authentication_none?
def ssl_client_authentication_none?
@ssl_client_authentication && @ssl_client_authentication.downcase == SSL_CLIENT_AUTH_NONE
end

def client_authentication_optional?
def ssl_client_authentication_optional?
@ssl_client_authentication && @ssl_client_authentication.downcase == SSL_CLIENT_AUTH_OPTIONAL
end

def provided_client_authentication_config(values = [@ssl_client_authentication])
def provided_ssl_client_authentication_config(values = [@ssl_client_authentication])
if original_params.include?('ssl_verify_mode')
['ssl_verify_mode', *values.map { |v| SSL_VERIFY_MODE_TO_CLIENT_AUTHENTICATION_MAP.key(v) }]
elsif original_params.include?('verify_mode')
Expand Down
2 changes: 2 additions & 0 deletions spec/fixtures/certs/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ echo "DO NOT USE THESE CERTIFICATES IN PRODUCTION" >> ./README.txt
# certificate authority
openssl genrsa -out root.key 4096
openssl req -new -x509 -days 1826 -extensions ca -key root.key -out root.crt -subj "/C=LS/ST=NA/L=Http Input/O=Logstash/CN=root" -config ../openssl.cnf
keytool -import -file root.crt -alias rootCA -keystore truststore.jks -noprompt -storepass 12345678
edmocosta marked this conversation as resolved.
Show resolved Hide resolved
edmocosta marked this conversation as resolved.
Show resolved Hide resolved

# server certificate from root
openssl genrsa -out server_from_root.key 4096
openssl req -new -key server_from_root.key -out server_from_root.csr -subj "/C=LS/ST=NA/L=Http Input/O=Logstash/CN=server" -config ../openssl.cnf
openssl x509 -req -extensions server_cert -extfile ../openssl.cnf -days 1096 -in server_from_root.csr -CA root.crt -CAkey root.key -set_serial 03 -out server_from_root.crt
openssl pkcs12 -export -out server_from_root.p12 -inkey server_from_root.key -in server_from_root.crt -certfile root.crt -password pass:12345678

# client certificate from root
openssl genrsa -out client_from_root.key 4096
Expand Down
Binary file not shown.
Binary file added spec/fixtures/certs/generated/truststore.jks
Binary file not shown.