diff --git a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java index 55859649986..2d7bafb1c5f 100644 --- a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java +++ b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java @@ -29,6 +29,7 @@ import java.net.InetSocketAddress; public class NettySecuredTransport extends NettyTransport { public static final String HOSTNAME_VERIFICATION_SETTING = "shield.ssl.hostname_verification"; + public static final String HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING = "shield.ssl.hostname_verification.resolve_name"; private final SSLService sslService; private final @Nullable IPFilter authenticator; @@ -112,9 +113,7 @@ public class NettySecuredTransport extends NettyTransport { SSLEngine sslEngine; if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) { InetSocketAddress inetSocketAddress = (InetSocketAddress) e.getValue(); - String hostname = inetSocketAddress.getHostName(); - int port = inetSocketAddress.getPort(); - sslEngine = sslService.createSSLEngine(ImmutableSettings.EMPTY, hostname, port); + sslEngine = sslService.createSSLEngine(ImmutableSettings.EMPTY, getHostname(inetSocketAddress), inetSocketAddress.getPort()); SSLParameters parameters = new SSLParameters(); parameters.setEndpointIdentificationAlgorithm("HTTPS"); sslEngine.setSSLParameters(parameters); @@ -128,6 +127,20 @@ public class NettySecuredTransport extends NettyTransport { ctx.sendDownstream(e); } + + private String getHostname(InetSocketAddress inetSocketAddress) { + String hostname; + if (settings.getAsBoolean(HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING, true)) { + hostname = inetSocketAddress.getHostName(); + } else { + hostname = inetSocketAddress.getHostString(); + } + + if (logger.isTraceEnabled()) { + logger.trace("resolved hostname [{}] for address [{}] to be used in ssl hostname verification", hostname, inetSocketAddress); + } + return hostname; + } } } } diff --git a/src/test/java/org/elasticsearch/shield/transport/netty/IPHostnameVerificationIntegrationTests.java b/src/test/java/org/elasticsearch/shield/transport/netty/IPHostnameVerificationIntegrationTests.java new file mode 100644 index 00000000000..b7d55e79577 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/transport/netty/IPHostnameVerificationIntegrationTests.java @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield.transport.netty; + +import org.elasticsearch.client.Client; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; +import org.elasticsearch.test.ShieldIntegrationTest; +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; +import static org.hamcrest.CoreMatchers.is; + +@ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE) +public class IPHostnameVerificationIntegrationTests extends ShieldIntegrationTest { + + static Path keystore; + + @Override + protected boolean sslTransportEnabled() { + return true; + } + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + Settings settings = super.nodeSettings(nodeOrdinal); + // The default Unicast test behavior is to use 'localhost' with the port number. For this test we need to use IP + String[] unicastAddresses = settings.getAsArray("discovery.zen.ping.unicast.hosts"); + for (int i = 0; i < unicastAddresses.length; i++) { + String address = unicastAddresses[i]; + unicastAddresses[i] = address.replace("localhost", "127.0.0.1"); + } + + ImmutableSettings.Builder settingsBuilder = settingsBuilder() + .put(settings) + .putArray("discovery.zen.ping.unicast.hosts", unicastAddresses); + + try { + //This keystore uses a cert with a CN of "Elasticsearch Test Node" and IPv4+IPv6 ip addresses as SubjectAlternativeNames + keystore = Paths.get(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.jks").toURI()); + assertThat(Files.exists(keystore), is(true)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return settingsBuilder.put("shield.ssl.keystore.path", keystore.toAbsolutePath()) // settings for client truststore + .put("shield.ssl.keystore.password", "testnode-ip-only") + .put("shield.ssl.truststore.path", keystore.toAbsolutePath()) // settings for client truststore + .put("shield.ssl.truststore.password", "testnode-ip-only") + .put("transport.host", "127.0.0.1") + .put("network.host", "127.0.0.1") + .put("shield.ssl.client.auth", "false") + .put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, true) + .put(NettySecuredTransport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING, false) + .build(); + } + + @Override + protected Settings transportClientSettings() { + return ImmutableSettings.builder().put(super.transportClientSettings()) + .put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, true) + .put(NettySecuredTransport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING, false) + .put("shield.ssl.keystore.path", keystore.toAbsolutePath()) + .put("shield.ssl.keystore.password", "testnode-ip-only") + .put("shield.ssl.truststore.path", keystore.toAbsolutePath()) + .put("shield.ssl.truststore.password", "testnode-ip-only") + .build(); + } + + @Test + public void testTransportClientConnectionWorksWithIPOnlyHostnameVerification() throws Exception { + Client client = internalCluster().transportClient(); + assertGreenClusterState(client); + } +} diff --git a/src/test/java/org/elasticsearch/test/ShieldSettingsSource.java b/src/test/java/org/elasticsearch/test/ShieldSettingsSource.java index ca929987460..89829ae702d 100644 --- a/src/test/java/org/elasticsearch/test/ShieldSettingsSource.java +++ b/src/test/java/org/elasticsearch/test/ShieldSettingsSource.java @@ -67,6 +67,7 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ private final byte[] systemKey; private final boolean sslTransportEnabled; private final boolean hostnameVerificationEnabled; + private final boolean hostnameVerificationResolveNameEnabled; /** * Creates a new {@link org.elasticsearch.test.SettingsSource} for the shield configuration. @@ -85,7 +86,8 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ this.parentFolder = parentFolder; this.subfolderPrefix = scope.name(); this.sslTransportEnabled = sslTransportEnabled; - hostnameVerificationEnabled = randomBoolean(); + this.hostnameVerificationEnabled = randomBoolean(); + this.hostnameVerificationResolveNameEnabled = randomBoolean(); } @Override @@ -190,11 +192,11 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ } private Settings getNodeSSLSettings() { - return getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode", sslTransportEnabled, hostnameVerificationEnabled); + return getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode", sslTransportEnabled, hostnameVerificationEnabled, hostnameVerificationResolveNameEnabled); } private Settings getClientSSLSettings() { - return getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient", sslTransportEnabled, hostnameVerificationEnabled); + return getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient", sslTransportEnabled, hostnameVerificationEnabled, hostnameVerificationResolveNameEnabled); } /** @@ -205,10 +207,10 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ * @return the configuration settings */ public static Settings getSSLSettingsForStore(String resourcePathToStore, String password) { - return getSSLSettingsForStore(resourcePathToStore, password, true, true); + return getSSLSettingsForStore(resourcePathToStore, password, true, true, true); } - private static Settings getSSLSettingsForStore(String resourcePathToStore, String password, boolean sslTransportEnabled, boolean hostnameVerificationEnabled) { + private static Settings getSSLSettingsForStore(String resourcePathToStore, String password, boolean sslTransportEnabled, boolean hostnameVerificationEnabled, boolean hostnameVerificationResolveNameEnabled) { File store; try { store = new File(ShieldSettingsSource.class.getResource(resourcePathToStore).toURI()); @@ -227,7 +229,8 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ if (sslTransportEnabled) { builder.put("shield.ssl.keystore.path", store.getPath()) .put("shield.ssl.keystore.password", password) - .put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, hostnameVerificationEnabled); + .put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, hostnameVerificationEnabled) + .put(NettySecuredTransport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING, hostnameVerificationResolveNameEnabled); } if (sslTransportEnabled && randomBoolean()) { diff --git a/src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.cert b/src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.cert new file mode 100644 index 00000000000..6ff84ba6674 --- /dev/null +++ b/src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.cert @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYjCCAkqgAwIBAgIJAKP3WbDN1WxEMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV +BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp +Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTUwMTE5MTQwNDE3WhcNMTkwMTE4MTQwNDE3 +WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV +BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3a5+yBl2rVEGwlOjw6Ji43+iqvaAbmVhnCk6laEa3GpzothX +7HhtGGDfjdhaLzWF5PWP8SvMM8g4f1PLN0hGSR7vrWjlnpvUDXIHsoIRqWfYdwDA +RNiUvOI4FBjN4pZ4sXyUYsTpw80l6W0r3zopyycE4+4HJv55U1Yy2/3qzv1IITqD +LwRt6VpbPGVyzDSBMQXEgfT7sfaJB9Ru+A/onIpEicrWhgCPHrBnSUkKCKNj9AX/ +RV6/yQYnS/KhLx/eQTP7NVcbrC2J4fFOLX9oZAj6dir/tYQ6rDAMqBTnbhGygDqP +0RgCVf82n6mA23n7l5DaZ4RZl+ssN3fNqDyDpQIDAQABo08wTTAJBgNVHRMEAjAA +MB0GA1UdDgQWBBSDFYaN/Od9ad7Kztv6cGjd2X4w1TAhBgNVHREEGjAYhwR/AAAB +hxAAAAAAAAAAAAAAAAAAAAACMA0GCSqGSIb3DQEBCwUAA4IBAQCbxk4VHMcdD2yU +VpLSBxHBdWY/Gn3f7k0WWhQAmRPR+S6vSr89hVO8UIkqEFzc+D19s9h0XvAmo2QO +G80OLTcHIjxiVqAVWoGUPH4D7FdG7sSBbrJbweIBMW8Ba4kefWGcI0KlUWTssFyE +YLJQIUCIdKtVf/qmcItvrEXw8ucSYaExvizMClTvf2fZxRws8Omo3dxn+ifUH5nn +evQW8Tawlx2ql5P6wHTSUclGLv1CXtMAnuOlyaYY/UbslNhSXigxYOZCI3ole3qL +Z0dBah+eCspOit14mi7jHPoS1Yji/CSh33KZD6ZESuv/V1B7oy6zZWei/b4vllW5 +RwZx1Y/r +-----END CERTIFICATE----- diff --git a/src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.jks b/src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.jks new file mode 100644 index 00000000000..7e757a37049 Binary files /dev/null and b/src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-ip-only.jks differ