blob: 149be86aa3171f7fe625728bd0b0c5a089b60d68 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.common;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.client.ClientConnectionException;
import org.apache.ignite.client.Config;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.client.SslMode;
import org.apache.ignite.configuration.ClientConfiguration;
import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.ConnectorConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.client.GridClient;
import org.apache.ignite.internal.client.GridClientConfiguration;
import org.apache.ignite.internal.client.GridClientFactory;
import org.apache.ignite.internal.processors.metric.MetricRegistry;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.metric.BooleanMetric;
import org.apache.ignite.spi.metric.HistogramMetric;
import org.apache.ignite.spi.metric.IntMetric;
import org.apache.ignite.spi.metric.LongMetric;
import org.apache.ignite.ssl.SslContextFactory;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
import static java.sql.DriverManager.getConnection;
import static org.apache.ignite.Ignition.startClient;
import static org.apache.ignite.internal.client.GridClientFactory.start;
import static org.apache.ignite.internal.managers.discovery.GridDiscoveryManager.DISCO_METRICS;
import static org.apache.ignite.internal.processors.metric.GridMetricManager.CLIENT_CONNECTOR_METRICS;
import static org.apache.ignite.internal.processors.rest.protocols.tcp.GridTcpRestProtocol.REST_CONNECTOR_METRIC_REGISTRY_NAME;
import static org.apache.ignite.internal.util.nio.GridNioServer.RECEIVED_BYTES_METRIC_NAME;
import static org.apache.ignite.internal.util.nio.GridNioServer.SENT_BYTES_METRIC_NAME;
import static org.apache.ignite.internal.util.nio.GridNioServer.SESSIONS_CNT_METRIC_NAME;
import static org.apache.ignite.internal.util.nio.GridNioServer.SSL_ENABLED_METRIC_NAME;
import static org.apache.ignite.internal.util.nio.ssl.GridNioSslFilter.SSL_HANDSHAKE_DURATION_HISTOGRAM_METRIC_NAME;
import static org.apache.ignite.internal.util.nio.ssl.GridNioSslFilter.SSL_REJECTED_SESSIONS_CNT_METRIC_NAME;
import static org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi.COMMUNICATION_METRICS_GROUP_NAME;
import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
import static org.apache.ignite.testframework.GridTestUtils.keyStorePassword;
import static org.apache.ignite.testframework.GridTestUtils.keyStorePath;
import static org.apache.ignite.testframework.GridTestUtils.sslTrustedFactory;
import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
/** Checks SSL metrics for various node connection approaches. */
public class NodeSslConnectionMetricTest extends GridCommonAbstractTest {
/** Cipher suite supported by cluster nodes. */
private static final String CIPHER_SUITE = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
/** Cipher suite not supported by cluster nodes. */
private static final String UNSUPPORTED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_GCM_SHA256";
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
super.beforeTestsStarted();
GridClientFactory.stopAll(false);
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
super.afterTest();
stopAllGrids(true);
}
/** Checks the status of the SSL metric if SSL is not configured on the node. */
@Test
public void testSslDisabled() throws Exception {
IgniteEx srv = startGrid();
MetricRegistry discoReg = mreg(srv, DISCO_METRICS);
assertFalse(discoReg.<BooleanMetric>findMetric("SslEnabled").value());
assertEquals(0, discoReg.<IntMetric>findMetric("RejectedSslConnectionsCount").value());
MetricRegistry commReg = mreg(srv, COMMUNICATION_METRICS_GROUP_NAME);
assertFalse(commReg.<BooleanMetric>findMetric(SSL_ENABLED_METRIC_NAME).value());
assertNull(commReg.<IntMetric>findMetric(SSL_REJECTED_SESSIONS_CNT_METRIC_NAME));
assertNull(commReg.<HistogramMetric>findMetric(SSL_HANDSHAKE_DURATION_HISTOGRAM_METRIC_NAME));
assertEquals(0, commReg.<IntMetric>findMetric(SESSIONS_CNT_METRIC_NAME).value());
MetricRegistry cliConnReg = mreg(srv, CLIENT_CONNECTOR_METRICS);
assertFalse(cliConnReg.<BooleanMetric>findMetric(SSL_ENABLED_METRIC_NAME).value());
assertNull(cliConnReg.<IntMetric>findMetric(SSL_REJECTED_SESSIONS_CNT_METRIC_NAME));
assertNull(cliConnReg.<HistogramMetric>findMetric(SSL_HANDSHAKE_DURATION_HISTOGRAM_METRIC_NAME));
assertEquals(0, cliConnReg.<IntMetric>findMetric(SESSIONS_CNT_METRIC_NAME).value());
MetricRegistry restConnReg = mreg(srv, REST_CONNECTOR_METRIC_REGISTRY_NAME);
assertNull(restConnReg.<BooleanMetric>findMetric(SSL_ENABLED_METRIC_NAME));
assertNull(restConnReg.<IntMetric>findMetric(SSL_REJECTED_SESSIONS_CNT_METRIC_NAME));
assertNull(restConnReg.<HistogramMetric>findMetric(SSL_HANDSHAKE_DURATION_HISTOGRAM_METRIC_NAME));
assertNull(restConnReg.<IntMetric>findMetric(SESSIONS_CNT_METRIC_NAME));
stopAllGrids();
srv = startGrid(getConfiguration().setConnectorConfiguration(new ConnectorConfiguration()));
restConnReg = mreg(srv, REST_CONNECTOR_METRIC_REGISTRY_NAME);
assertFalse(restConnReg.<BooleanMetric>findMetric(SSL_ENABLED_METRIC_NAME).value());
assertEquals(0, restConnReg.<IntMetric>findMetric(SESSIONS_CNT_METRIC_NAME).value());
}
/** Tests SSL metrics produced by JDBC connection. */
@Test
public void testJdbc() throws Exception {
MetricRegistry reg = mreg(startClusterNode(0), CLIENT_CONNECTOR_METRICS);
assertEquals(0, reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value());
assertEquals(0, reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value());
try (Connection ignored = getConnection(jdbcConfiguration("thinClient", "trusttwo", CIPHER_SUITE, "TLSv1.2"))) {
checkSslCommunicationMetrics(reg, 1, 1, 0);
}
assertTrue(reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value() > 0);
assertTrue(reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value() > 0);
checkSslCommunicationMetrics(reg, 1, 0, 0);
// Tests untrusted certificate.
assertThrowsWithCause(() ->
getConnection(jdbcConfiguration("client", "trusttwo", CIPHER_SUITE, "TLSv1.2")),
SQLException.class);
checkSslCommunicationMetrics(reg, 2, 0, 1);
// Tests unsupported cipher suite.
assertThrowsWithCause(() ->
getConnection(jdbcConfiguration("thinClient", "trusttwo", UNSUPPORTED_CIPHER_SUITE, "TLSv1.2")),
SQLException.class);
checkSslCommunicationMetrics(reg, 3, 0, 2);
assertThrowsWithCause(() ->
getConnection(jdbcConfiguration("thinClient", "trusttwo", null, "TLSv1.1")),
SQLException.class);
checkSslCommunicationMetrics(reg, 4, 0, 3);
}
/** Tests SSL metrics produced by REST TCP client connection. */
@Test
public void testRestClientConnector() throws Exception {
MetricRegistry reg = mreg(startClusterNode(0), REST_CONNECTOR_METRIC_REGISTRY_NAME);
assertEquals(0, reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value());
assertEquals(0, reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value());
try (
GridClient ignored = start(gridClientConfiguration("connectorClient", "trustthree", CIPHER_SUITE, "TLSv1.2"))
) {
checkSslCommunicationMetrics(reg, 1, 1, 0);
}
assertTrue(reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value() > 0);
assertTrue(reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value() > 0);
checkSslCommunicationMetrics(reg, 1, 0, 0);
// Tests untrusted certificate.
try (GridClient ignored = start(gridClientConfiguration("client", "trustthree", CIPHER_SUITE, "TLSv1.2"))) {
// GridClient makes 2 additional silent connection attempts if an SSL error occurs.
}
checkSslCommunicationMetrics(reg, 4, 0, 3);
// Tests unsupported cipher suite.
try (
GridClient ignored = start(gridClientConfiguration("connectorClient", "trustthree",
UNSUPPORTED_CIPHER_SUITE, "TLSv1.2"))
) {
// GridClient makes 2 additional silent connection attempts if an SSL error occurs.
}
checkSslCommunicationMetrics(reg, 7, 0, 6);
// Tests mismatched protocol versions.
try (GridClient ignored = start(gridClientConfiguration("connectorClient", "trustthree", null, "TLSv1.1"))) {
// GridClient makes 2 additional silent connection attempts if an SSL error occurs.
}
checkSslCommunicationMetrics(reg, 10, 0, 9);
}
/** Tests SSL discovery metrics produced by node connection. */
@Test
public void testDiscovery() throws Exception {
MetricRegistry reg = mreg(startClusterNode(0), DISCO_METRICS);
startGrid(nodeConfiguration(1, true, "client", "trustone", CIPHER_SUITE, "TLSv1.2"));
assertTrue(reg.<BooleanMetric>findMetric("SslEnabled").value());
assertEquals(0, reg.<IntMetric>findMetric("RejectedSslConnectionsCount").value());
// Tests untrusted certificate.
checkNodeJoinFails(2, true, "thinClient", "trusttwo", CIPHER_SUITE, "TLSv1.2");
checkNodeJoinFails(2, false, "thinClient", "trusttwo", CIPHER_SUITE, "TLSv1.2");
// Tests untrusted cipher suites.
checkNodeJoinFails(2, true, "client", "trustone", UNSUPPORTED_CIPHER_SUITE, "TLSv1.2");
checkNodeJoinFails(2, false, "node01", "trustone", UNSUPPORTED_CIPHER_SUITE, "TLSv1.2");
// Tests mismatched protocol versions.
checkNodeJoinFails(2, true, "client", "trustone", null, "TLSv1.1");
checkNodeJoinFails(2, false, "node01", "trustone", null, "TLSv1.1");
// In case of an SSL error, the client and server nodes make 2 additional connection attempts.
assertTrue(waitForCondition(() ->
18 == reg.<IntMetric>findMetric("RejectedSslConnectionsCount").value(),
getTestTimeout()));
}
/** Tests SSL communication metrics produced by node connection. */
@Test
public void testCommunication() throws Exception {
MetricRegistry reg = mreg(startClusterNode(0), COMMUNICATION_METRICS_GROUP_NAME);
assertEquals(0, reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value());
assertEquals(0, reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value());
checkSslCommunicationMetrics(reg, 0, 0, 0);
try (
IgniteEx cliNode = startGrid(nodeConfiguration(1, true, "client", "trustone", CIPHER_SUITE, "TLSv1.2"));
IgniteEx srvNode = startGrid(nodeConfiguration(2, false, "node01", "trustone", CIPHER_SUITE, "TLSv1.2"))
) {
checkSslCommunicationMetrics(reg, 2, 2, 0);
MetricRegistry cliNodeReg = mreg(cliNode, COMMUNICATION_METRICS_GROUP_NAME);
checkSslCommunicationMetrics(cliNodeReg, 0, 1, 0);
assertTrue(cliNodeReg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value() > 0);
assertTrue(cliNodeReg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value() > 0);
MetricRegistry srvNodeReg = mreg(srvNode, COMMUNICATION_METRICS_GROUP_NAME);
checkSslCommunicationMetrics(srvNodeReg, 0, 1, 0);
assertTrue(srvNodeReg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value() > 0);
assertTrue(srvNodeReg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value() > 0);
}
assertTrue(reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value() > 0);
assertTrue(reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value() > 0);
checkSslCommunicationMetrics(reg, 2, 0, 0);
}
/** Tests SSL metrics produced by thin client connection. */
@Test
public void testClientConnector() throws Exception {
MetricRegistry reg = mreg(startClusterNode(0), CLIENT_CONNECTOR_METRICS);
assertEquals(0, reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value());
assertEquals(0, reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value());
try (IgniteClient ignored = startClient(clientConfiguration("thinClient", "trusttwo", CIPHER_SUITE, "TLSv1.2"))) {
checkSslCommunicationMetrics(reg, 1, 1, 0);
}
assertTrue(reg.<LongMetric>findMetric(SENT_BYTES_METRIC_NAME).value() > 0);
assertTrue(reg.<LongMetric>findMetric(RECEIVED_BYTES_METRIC_NAME).value() > 0);
checkSslCommunicationMetrics(reg, 1, 0, 0);
// Tests untrusted certificate.
assertThrowsWithCause(() ->
startClient(clientConfiguration("client", "trustboth", CIPHER_SUITE, "TLSv1.2")),
ClientConnectionException.class);
checkSslCommunicationMetrics(reg, 2, 0, 1);
// Tests unsupported cipher suites.
assertThrowsWithCause(() ->
startClient(clientConfiguration("thinClient", "trusttwo", UNSUPPORTED_CIPHER_SUITE, "TLSv1.2")),
ClientConnectionException.class
);
checkSslCommunicationMetrics(reg, 3, 0, 2);
// Tests mismatched protocol versions.
assertThrowsWithCause(() ->
startClient(clientConfiguration("thinClient", "trusttwo", null, "TLSv1.1")),
ClientConnectionException.class
);
checkSslCommunicationMetrics(reg, 4, 0, 3);
}
/** Starts node that imitates a cluster server node to which connections will be performed. */
private IgniteEx startClusterNode(int idx) throws Exception {
IgniteConfiguration cfg = getConfiguration(getTestIgniteInstanceName(idx));
cfg.setSslContextFactory(sslContextFactory("server", "trustone", CIPHER_SUITE, "TLSv1.2"));
cfg.setClientConnectorConfiguration(new ClientConnectorConfiguration()
.setSslEnabled(true)
.setSslClientAuth(true)
.setUseIgniteSslContextFactory(false)
.setSslContextFactory(sslContextFactory("thinServer", "trusttwo", CIPHER_SUITE, "TLSv1.2")));
cfg.setConnectorConfiguration(new ConnectorConfiguration()
.setSslClientAuth(true)
.setSslEnabled(true)
.setSslFactory(sslContextFactory("connectorServer", "trustthree", CIPHER_SUITE, "TLSv1.2")));
return startGrid(cfg);
}
/** @return JDBC connection configuration with specified SSL options. */
private String jdbcConfiguration(String keyStore, String trustStore, String cipherSuite, String protocol) {
String res = "jdbc:ignite:thin://" + Config.SERVER + "?sslMode=require" +
"&sslClientCertificateKeyStoreUrl=" + keyStorePath(keyStore) +
"&sslClientCertificateKeyStorePassword=" + keyStorePassword() +
"&sslTrustCertificateKeyStoreUrl=" + keyStorePath(trustStore) +
"&sslTrustCertificateKeyStorePassword=" + keyStorePassword() +
"&sslProtocol=" + protocol;
if (cipherSuite != null)
res += "&sslCipherSuites=" + cipherSuite;
return res;
}
/** @return Node connection configuration with specified SSL options. */
private IgniteConfiguration nodeConfiguration(
int idx,
boolean client,
String keyStore,
String trustStore,
String cipherSuite,
String protocol
) throws Exception {
return getConfiguration(getTestIgniteInstanceName(idx))
.setSslContextFactory(sslContextFactory(keyStore, trustStore, cipherSuite, protocol))
.setClientMode(client);
}
/** @return Grid client connection configuration with specified SSL options. */
private GridClientConfiguration gridClientConfiguration(
String keyStore,
String trustStore,
String cipherSuite,
String protocol
) {
SslContextFactory sslCtxFactory = sslContextFactory(keyStore, trustStore, cipherSuite, protocol);
return new GridClientConfiguration()
.setServers(Collections.singleton("127.0.0.1:11211"))
.setSslContextFactory(sslCtxFactory::create);
}
/** @return Thin client connection configuration with specified SSL options. */
private ClientConfiguration clientConfiguration(
String keyStore,
String trustStore,
String cipherSuite,
String protocol
) {
return new ClientConfiguration()
.setAddresses("127.0.0.1:10800")
.setSslMode(SslMode.REQUIRED)
.setSslContextFactory(sslContextFactory(keyStore, trustStore, cipherSuite, protocol));
}
/** Checks that the node join failed if the connection was performed with the specified SSL options. */
@SuppressWarnings("ThrowableNotThrown")
private void checkNodeJoinFails(
int idx,
boolean client,
String keyStore,
String trustStore,
String cipherSuite,
String protocol
) throws Exception {
IgniteConfiguration cfg = nodeConfiguration(idx, client, keyStore, trustStore, cipherSuite, protocol);
if (client)
((TcpDiscoverySpi)cfg.getDiscoverySpi()).setJoinTimeout(1); // To prevent client multiple join attempts.
GridTestUtils.assertThrowsWithCause(() -> startGrid(cfg), IgniteCheckedException.class);
}
/** Obtains the metric registry with the specified name from Ignite instance. */
private MetricRegistry mreg(IgniteEx ignite, String name) {
return ignite.context().metric().registry(name);
}
/** Checks SSL communication metrics. */
private void checkSslCommunicationMetrics(
MetricRegistry mreg,
long handshakeCnt,
int sesCnt,
int rejectedSesCnt
) throws Exception {
assertEquals(true, mreg.<BooleanMetric>findMetric(SSL_ENABLED_METRIC_NAME).value());
assertTrue(waitForCondition(() ->
sesCnt == mreg.<IntMetric>findMetric(SESSIONS_CNT_METRIC_NAME).value(),
getTestTimeout()));
assertTrue(waitForCondition(() ->
handshakeCnt == Arrays.stream(
mreg.<HistogramMetric>findMetric(SSL_HANDSHAKE_DURATION_HISTOGRAM_METRIC_NAME).value()
).sum(),
getTestTimeout()));
assertTrue(waitForCondition(() ->
rejectedSesCnt == mreg.<IntMetric>findMetric(SSL_REJECTED_SESSIONS_CNT_METRIC_NAME).value(),
getTestTimeout()));
}
/** Creates {@link SslContextFactory} with specified options. */
private SslContextFactory sslContextFactory(String keyStore, String trustStore, String cipherSuite, String protocol) {
SslContextFactory res = (SslContextFactory)sslTrustedFactory(keyStore, trustStore);
if (cipherSuite != null)
res.setCipherSuites(cipherSuite);
res.setProtocols(protocol);
return res;
}
}