You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ap...@apache.org on 2022/01/28 00:31:31 UTC
[pinot] 01/02: prototype
This is an automated email from the ASF dual-hosted git repository.
apucher pushed a commit to branch listener-tls-customization
in repository https://gitbox.apache.org/repos/asf/pinot.git
commit 7a18a16e9fe160130f9a72864dbb7938963e0d4f
Author: Alexander Pucher <ap...@apache.org>
AuthorDate: Thu Jan 27 15:32:11 2022 -0800
prototype
---
.../org/apache/pinot/core/transport/TlsConfig.java | 23 +-
.../apache/pinot/core/util/ListenerConfigUtil.java | 59 +++-
.../java/org/apache/pinot/core/util/TlsUtils.java | 77 +++--
.../tests/BasicAuthTlsRealtimeIntegrationTest.java | 83 ++---
.../integration/tests/TlsIntegrationTest.java | 354 +++++++++++++++++++++
.../CertBasedTlsChannelAccessControlFactory.java | 3 +-
.../src/test/resources/empty.jks | Bin 0 -> 32 bytes
.../src/test/resources/empty.p12 | Bin 0 -> 103 bytes
.../src/test/resources/tlstest.jks | Bin 2277 -> 2283 bytes
.../src/test/resources/tlstest.p12 | Bin 10155 -> 2645 bytes
10 files changed, 487 insertions(+), 112 deletions(-)
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/TlsConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/TlsConfig.java
index c8de43b..9b86c34 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/transport/TlsConfig.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/TlsConfig.java
@@ -18,6 +18,8 @@
*/
package org.apache.pinot.core.transport;
+import io.netty.handler.ssl.SslProvider;
+import java.security.KeyStore;
import org.apache.commons.lang3.StringUtils;
@@ -26,13 +28,28 @@ import org.apache.commons.lang3.StringUtils;
*/
public class TlsConfig {
private boolean _clientAuthEnabled;
- private String _keyStoreType;
+ private String _keyStoreType = KeyStore.getDefaultType();
private String _keyStorePath;
private String _keyStorePassword;
- private String _trustStoreType;
+ private String _trustStoreType = KeyStore.getDefaultType();
private String _trustStorePath;
private String _trustStorePassword;
- private String _sslProvider;
+ private String _sslProvider = SslProvider.JDK.toString();
+
+ public TlsConfig() {
+ // left blank
+ }
+
+ public TlsConfig(TlsConfig tlsConfig) {
+ _clientAuthEnabled = tlsConfig._clientAuthEnabled;
+ _keyStoreType = tlsConfig._keyStoreType;
+ _keyStorePath = tlsConfig._keyStorePath;
+ _keyStorePassword = tlsConfig._keyStorePassword;
+ _trustStoreType = tlsConfig._trustStoreType;
+ _trustStorePath = tlsConfig._trustStorePath;
+ _trustStorePassword = tlsConfig._trustStorePassword;
+ _sslProvider = tlsConfig._sslProvider;
+ }
public boolean isClientAuthEnabled() {
return _clientAuthEnabled;
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java b/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java
index 0211c29..0fd1143 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java
@@ -19,7 +19,14 @@
package org.apache.pinot.core.util;
import com.google.common.base.Preconditions;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -28,6 +35,7 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.pinot.core.transport.ListenerConfig;
import org.apache.pinot.core.transport.TlsConfig;
@@ -76,9 +84,7 @@ public final class ListenerConfigUtil {
String[] protocols = config.getProperty(namespace + DOT_ACCESS_PROTOCOLS).split(",");
- return Arrays.stream(protocols).peek(protocol -> Preconditions
- .checkArgument(SUPPORTED_PROTOCOLS.contains(protocol), "Unsupported protocol '%s' in config namespace '%s'",
- protocol, namespace)).map(protocol -> buildListenerConfig(config, namespace, protocol, tlsDefaults))
+ return Arrays.stream(protocols).map(protocol -> buildListenerConfig(config, namespace, protocol, tlsDefaults))
.collect(Collectors.toList());
}
@@ -167,24 +173,35 @@ public final class ListenerConfigUtil {
return listeners;
}
- private static ListenerConfig buildListenerConfig(PinotConfiguration config, String namespace, String protocol,
+ private static ListenerConfig buildListenerConfig(PinotConfiguration config, String namespace, String name,
TlsConfig tlsConfig) {
- String protocolNamespace = namespace + DOT_ACCESS_PROTOCOLS + "." + protocol;
+ String protocolNamespace = namespace + DOT_ACCESS_PROTOCOLS + "." + name;
- return new ListenerConfig(protocol, getHost(config.getProperty(protocolNamespace + ".host", DEFAULT_HOST)),
- getPort(config.getProperty(protocolNamespace + ".port")), protocol, tlsConfig);
+ return new ListenerConfig(name, getHost(config.getProperty(protocolNamespace + ".host", DEFAULT_HOST)),
+ getPort(config.getProperty(protocolNamespace + ".port")), getProtocol(config.getProperty(protocolNamespace + ".protocol"), name),
+ TlsUtils.extractTlsConfig(config, namespace + ".tls", tlsConfig));
}
private static String getHost(String configuredHost) {
- return Optional.ofNullable(configuredHost).filter(host -> !host.trim().isEmpty())
+ return Optional.ofNullable(configuredHost).map(String::trim).filter(host -> !host.isEmpty())
.orElseThrow(() -> new IllegalArgumentException(configuredHost + " is not a valid host"));
}
private static int getPort(String configuredPort) {
- return Optional.ofNullable(configuredPort).filter(port -> !port.trim().isEmpty()).<Integer>map(Integer::valueOf)
+ return Optional.ofNullable(configuredPort).map(String::trim).filter(port -> !port.isEmpty()).map(Integer::valueOf)
.orElseThrow(() -> new IllegalArgumentException(configuredPort + " is not a valid port"));
}
+ private static String getProtocol(String configuredProtocol, String listenerName) {
+ Optional<String> optProtocol = Optional.ofNullable(configuredProtocol).map(String::trim).filter(protocol -> !protocol.isEmpty());
+ if (optProtocol.isEmpty()) {
+ return Optional.of(listenerName).filter(SUPPORTED_PROTOCOLS::contains)
+ .orElseThrow(() -> new IllegalArgumentException("No protocol set for listener" + listenerName + " and '" + listenerName + "' is not a valid protocol either"));
+ }
+ return optProtocol.filter(SUPPORTED_PROTOCOLS::contains)
+ .orElseThrow(() -> new IllegalArgumentException(configuredProtocol + " is not a valid protocol"));
+ }
+
public static HttpServer buildHttpServer(ResourceConfig resConfig, List<ListenerConfig> listenerConfigs) {
Preconditions.checkNotNull(listenerConfigs);
@@ -213,23 +230,24 @@ public final class ListenerConfigUtil {
if (CommonConstants.HTTPS_PROTOCOL.equals(listenerConfig.getProtocol())) {
listener.setSecure(true);
- listener.setSSLEngineConfig(buildSSLConfig(listenerConfig.getTlsConfig()));
+ listener.setSSLEngineConfig(buildSSLEngineConfigurator(listenerConfig.getTlsConfig()));
}
httpServer.addListener(listener);
}
- private static SSLEngineConfigurator buildSSLConfig(TlsConfig tlsConfig) {
+ private static SSLEngineConfigurator buildSSLEngineConfigurator(TlsConfig tlsConfig) {
SSLContextConfigurator sslContextConfigurator = new SSLContextConfigurator();
if (tlsConfig.getKeyStorePath() != null) {
Preconditions.checkNotNull(tlsConfig.getKeyStorePassword(), "key store password required");
- sslContextConfigurator.setKeyStoreFile(tlsConfig.getKeyStorePath());
+ sslContextConfigurator.setKeyStoreFile(cacheInTempFile(tlsConfig.getKeyStorePath()).getAbsolutePath());
sslContextConfigurator.setKeyStorePass(tlsConfig.getKeyStorePassword());
}
+
if (tlsConfig.getTrustStorePath() != null) {
Preconditions.checkNotNull(tlsConfig.getKeyStorePassword(), "trust store password required");
- sslContextConfigurator.setTrustStoreFile(tlsConfig.getTrustStorePath());
+ sslContextConfigurator.setTrustStoreFile(cacheInTempFile(tlsConfig.getTrustStorePath()).getAbsolutePath());
sslContextConfigurator.setTrustStorePass(tlsConfig.getTrustStorePassword());
}
@@ -242,4 +260,19 @@ public final class ListenerConfigUtil {
.map(listener -> String.format("%s://%s:%d", listener.getProtocol(), listener.getHost(), listener.getPort()))
.toArray(), ", ");
}
+
+ private static File cacheInTempFile(String sourceUrl) {
+ try {
+ File tempFile = Files.createTempFile("keystore", "cache").toFile();
+ tempFile.deleteOnExit();
+
+ try (InputStream is = TlsUtils.makeKeyStoreUrl(sourceUrl).openStream(); OutputStream os = new FileOutputStream(tempFile)) {
+ IOUtils.copy(is, os);
+ }
+
+ return tempFile;
+ } catch (Exception e) {
+ throw new IllegalStateException(String.format("Could not retrieve and cache keystore from '%s'", sourceUrl), e);
+ }
+ }
}
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/TlsUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/util/TlsUtils.java
index a004404..5bf3395 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/util/TlsUtils.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/util/TlsUtils.java
@@ -19,14 +19,16 @@
package org.apache.pinot.core.util;
import com.google.common.base.Preconditions;
-import io.netty.handler.ssl.SslProvider;
-import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketAddress;
-import java.net.UnknownHostException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import javax.net.ssl.HttpsURLConnection;
@@ -36,10 +38,10 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
-import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.apache.commons.lang.StringUtils;
import org.apache.pinot.common.utils.FileUploadDownloadClient;
import org.apache.pinot.core.transport.TlsConfig;
import org.apache.pinot.spi.env.PinotConfiguration;
@@ -72,31 +74,28 @@ public final class TlsUtils {
* @return TlsConfig instance
*/
public static TlsConfig extractTlsConfig(PinotConfiguration pinotConfig, String namespace) {
- TlsConfig tlsConfig = new TlsConfig();
-
- tlsConfig.setClientAuthEnabled(pinotConfig.getProperty(key(namespace, CLIENT_AUTH_ENABLED), false));
-
- tlsConfig.setKeyStoreType(pinotConfig.getProperty(key(namespace, KEYSTORE_TYPE), KeyStore.getDefaultType()));
-
- if (pinotConfig.containsKey(key(namespace, KEYSTORE_PATH))) {
- tlsConfig.setKeyStorePath(pinotConfig.getProperty(key(namespace, KEYSTORE_PATH)));
- }
-
- if (pinotConfig.containsKey(key(namespace, KEYSTORE_PASSWORD))) {
- tlsConfig.setKeyStorePassword(pinotConfig.getProperty(key(namespace, KEYSTORE_PASSWORD)));
- }
-
- tlsConfig.setTrustStoreType(pinotConfig.getProperty(key(namespace, TRUSTSTORE_TYPE), KeyStore.getDefaultType()));
-
- if (pinotConfig.containsKey(key(namespace, TRUSTSTORE_PATH))) {
- tlsConfig.setTrustStorePath(pinotConfig.getProperty(key(namespace, TRUSTSTORE_PATH)));
- }
-
- if (pinotConfig.containsKey(key(namespace, TRUSTSTORE_PASSWORD))) {
- tlsConfig.setTrustStorePassword(pinotConfig.getProperty(key(namespace, TRUSTSTORE_PASSWORD)));
- }
+ return extractTlsConfig(pinotConfig, namespace, new TlsConfig());
+ }
- tlsConfig.setSslProvider(pinotConfig.getProperty(key(namespace, SSL_PROVIDER), SslProvider.JDK.toString()));
+ /**
+ * Extract a TlsConfig instance from a namespaced set of configuration keys, based on a default config
+ *
+ * @param pinotConfig pinot configuration
+ * @param namespace namespace prefix
+ * @param defaultConfig TLS config defaults
+ *
+ * @return TlsConfig instance
+ */
+ public static TlsConfig extractTlsConfig(PinotConfiguration pinotConfig, String namespace, TlsConfig defaultConfig) {
+ TlsConfig tlsConfig = new TlsConfig(defaultConfig);
+ tlsConfig.setClientAuthEnabled(pinotConfig.getProperty(key(namespace, CLIENT_AUTH_ENABLED), defaultConfig.isClientAuthEnabled()));
+ tlsConfig.setKeyStoreType(pinotConfig.getProperty(key(namespace, KEYSTORE_TYPE), defaultConfig.getKeyStoreType()));
+ tlsConfig.setKeyStorePath(pinotConfig.getProperty(key(namespace, KEYSTORE_PATH), defaultConfig.getKeyStorePath()));
+ tlsConfig.setKeyStorePassword(pinotConfig.getProperty(key(namespace, KEYSTORE_PASSWORD), defaultConfig.getKeyStorePassword()));
+ tlsConfig.setTrustStoreType(pinotConfig.getProperty(key(namespace, TRUSTSTORE_TYPE), defaultConfig.getTrustStoreType()));
+ tlsConfig.setTrustStorePath(pinotConfig.getProperty(key(namespace, TRUSTSTORE_PATH), defaultConfig.getTrustStorePath()));
+ tlsConfig.setTrustStorePassword(pinotConfig.getProperty(key(namespace, TRUSTSTORE_PASSWORD), defaultConfig.getTrustStorePassword()));
+ tlsConfig.setSslProvider(pinotConfig.getProperty(key(namespace, SSL_PROVIDER), defaultConfig.getSslProvider()));
return tlsConfig;
}
@@ -128,7 +127,7 @@ public final class TlsUtils {
try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
- try (FileInputStream is = new FileInputStream(keyStorePath)) {
+ try (InputStream is = makeKeyStoreUrl(keyStorePath).openStream()) {
keyStore.load(is, keyStorePassword.toCharArray());
}
@@ -168,7 +167,7 @@ public final class TlsUtils {
try {
KeyStore keyStore = KeyStore.getInstance(trustStoreType);
- try (FileInputStream is = new FileInputStream(trustStorePath)) {
+ try (InputStream is = makeKeyStoreUrl(trustStorePath).openStream()) {
keyStore.load(is, trustStorePassword.toCharArray());
}
@@ -237,6 +236,18 @@ public final class TlsUtils {
return namespace + "." + suffix;
}
+ public static URL makeKeyStoreUrl(String storePath)
+ throws URISyntaxException, MalformedURLException {
+ URI inputUri = new URI(storePath);
+ if (StringUtils.isBlank(inputUri.getScheme())) {
+ if (storePath.startsWith("/")) {
+ return new URL("file://" + storePath);
+ }
+ return new URL("file://./" + storePath);
+ }
+ return inputUri.toURL();
+ }
+
/**
* Adapted from: https://svn.apache.org/viewvc/httpcomponents/oac
* .hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLProtocolSocketFactory.java?view=markup
@@ -250,14 +261,14 @@ public final class TlsUtils {
@Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort)
- throws IOException, UnknownHostException {
+ throws IOException {
return _sslSocketFactory.createSocket(host, port, localAddress, localPort);
}
@Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort,
HttpConnectionParams params)
- throws IOException, UnknownHostException, ConnectTimeoutException {
+ throws IOException {
Preconditions.checkNotNull(params);
int timeout = params.getConnectionTimeout();
@@ -275,7 +286,7 @@ public final class TlsUtils {
@Override
public Socket createSocket(String host, int port)
- throws IOException, UnknownHostException {
+ throws IOException {
return _sslSocketFactory.createSocket(host, port);
}
}
diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BasicAuthTlsRealtimeIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BasicAuthTlsRealtimeIntegrationTest.java
index 003a1d2..354c55a 100644
--- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BasicAuthTlsRealtimeIntegrationTest.java
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BasicAuthTlsRealtimeIntegrationTest.java
@@ -19,20 +19,16 @@
package org.apache.pinot.integration.tests;
import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.base.Preconditions;
import groovy.lang.IntRange;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
import org.apache.pinot.client.Connection;
import org.apache.pinot.client.ConnectionFactory;
import org.apache.pinot.client.JsonAsyncHttpPinotClientTransportFactory;
@@ -62,17 +58,12 @@ import static org.apache.pinot.integration.tests.BasicAuthTestUtils.AUTH_HEADER;
* to go, you can manually run BasicAuthRealtimeIntegrationTest which tests the auth aspect only.
*/
public class BasicAuthTlsRealtimeIntegrationTest extends BaseClusterIntegrationTest {
- private final File _tempDirTls = new File(FileUtils.getTempDirectory(), getClass().getSimpleName() + "-cert");
- private final File _tlsStore = _tempDirTls.toPath().resolve("tlsstore.p12").toFile();
- private final File _tlsStoreJKS = _tempDirTls.toPath().resolve("tlsstore.jks").toFile();
+ private final URL _tlsStorePKCS12 = TlsIntegrationTest.class.getResource("/tlstest.p12");
@BeforeClass
public void setUp()
throws Exception {
TestUtils.ensureDirectoriesExistAndEmpty(_tempDir);
- TestUtils.ensureDirectoriesExistAndEmpty(_tempDirTls);
-
- prepareTlsStore();
// Start Zookeeper
startZk();
@@ -107,34 +98,38 @@ public class BasicAuthTlsRealtimeIntegrationTest extends BaseClusterIntegrationT
stopKafka();
stopZk();
FileUtils.deleteDirectory(_tempDir);
- FileUtils.deleteDirectory(_tempDirTls);
}
@Override
public Map<String, Object> getDefaultControllerConfiguration() {
Map<String, Object> prop = super.getDefaultControllerConfiguration();
- prop.put("controller.tls.keystore.path", _tlsStore.getAbsolutePath());
+ prop.put("controller.tls.keystore.path", _tlsStorePKCS12);
prop.put("controller.tls.keystore.password", "changeit");
- prop.put("controller.tls.truststore.path", _tlsStore.getAbsolutePath());
+ prop.put("controller.tls.keystore.type", "PKCS12");
+ prop.put("controller.tls.truststore.path", _tlsStorePKCS12);
prop.put("controller.tls.truststore.password", "changeit");
+ prop.put("controller.tls.truststore.type", "PKCS12");
- prop.remove("controller.port");
prop.put("controller.access.protocols", "https");
prop.put("controller.access.protocols.https.port", DEFAULT_CONTROLLER_PORT);
prop.put("controller.broker.protocol", "https");
prop.put("controller.vip.protocol", "https");
prop.put("controller.vip.port", DEFAULT_CONTROLLER_PORT);
+ prop.remove("controller.port");
+
return BasicAuthTestUtils.addControllerConfiguration(prop);
}
@Override
protected PinotConfiguration getDefaultBrokerConfiguration() {
Map<String, Object> prop = super.getDefaultBrokerConfiguration().toMap();
- prop.put("pinot.broker.tls.keystore.path", _tlsStore.getAbsolutePath());
+ prop.put("pinot.broker.tls.keystore.path", _tlsStorePKCS12);
prop.put("pinot.broker.tls.keystore.password", "changeit");
- prop.put("pinot.broker.tls.truststore.path", _tlsStore.getAbsolutePath());
+ prop.put("pinot.server.tls.keystore.type", "PKCS12");
+ prop.put("pinot.broker.tls.truststore.path", _tlsStorePKCS12);
prop.put("pinot.broker.tls.truststore.password", "changeit");
+ prop.put("pinot.broker.tls.truststore.type", "PKCS12");
prop.put("pinot.broker.client.access.protocols", "https");
prop.put("pinot.broker.client.access.protocols.https.port", DEFAULT_BROKER_PORT);
@@ -146,15 +141,16 @@ public class BasicAuthTlsRealtimeIntegrationTest extends BaseClusterIntegrationT
@Override
protected PinotConfiguration getDefaultServerConfiguration() {
Map<String, Object> prop = super.getDefaultServerConfiguration().toMap();
- prop.put("pinot.server.tls.keystore.path", _tlsStoreJKS.getAbsolutePath());
+ prop.put("pinot.server.tls.keystore.path", _tlsStorePKCS12);
prop.put("pinot.server.tls.keystore.password", "changeit");
prop.put("pinot.server.tls.keystore.type", "PKCS12");
- prop.put("pinot.server.tls.truststore.path", _tlsStore.getAbsolutePath());
+ prop.put("pinot.server.tls.truststore.path", _tlsStorePKCS12);
prop.put("pinot.server.tls.truststore.password", "changeit");
- prop.put("pinot.server.admin.access.control.factory.class",
- CertBasedTlsChannelAccessControlFactory.class.getName());
+ prop.put("pinot.server.tls.truststore.type", "PKCS12");
prop.put("pinot.server.tls.client.auth.enabled", "true");
+ prop.put("pinot.server.admin.access.control.factory.class",
+ CertBasedTlsChannelAccessControlFactory.class.getName());
prop.put("pinot.server.adminapi.access.protocols", "https");
prop.put("pinot.server.adminapi.access.protocols.https.port", "7443");
prop.put("pinot.server.netty.enabled", "false");
@@ -168,10 +164,12 @@ public class BasicAuthTlsRealtimeIntegrationTest extends BaseClusterIntegrationT
@Override
protected PinotConfiguration getDefaultMinionConfiguration() {
Map<String, Object> prop = super.getDefaultMinionConfiguration().toMap();
- prop.put("pinot.minion.tls.keystore.path", _tlsStore.getAbsolutePath());
+ prop.put("pinot.minion.tls.keystore.path", _tlsStorePKCS12);
prop.put("pinot.minion.tls.keystore.password", "changeit");
- prop.put("pinot.minion.tls.truststore.path", _tlsStore.getAbsolutePath());
+ prop.put("pinot.server.tls.keystore.type", "PKCS12");
+ prop.put("pinot.minion.tls.truststore.path", _tlsStorePKCS12);
prop.put("pinot.minion.tls.truststore.password", "changeit");
+ prop.put("pinot.server.tls.truststore.type", "PKCS12");
return BasicAuthTestUtils.addMinionConfiguration(prop);
}
@@ -260,43 +258,4 @@ public class BasicAuthTlsRealtimeIntegrationTest extends BaseClusterIntegrationT
> 200000); // download segment
}
}
-
- void prepareTlsStore()
- throws Exception {
- try (OutputStream os = new FileOutputStream(_tlsStore);
- /*
- * Command to generate the tlstest.jks file (generate key pairs for both IPV4 and IPV6 addresses):
- * ```
- * keytool -genkeypair -keystore tlstest.jks -dname "CN=test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, \
- * C=Unknown" -keypass changeit -storepass changeit -keyalg RSA -alias localhost-ipv4 -ext \
- * SAN=dns:localhost,ip:127.0.0.1
- *
- * keytool -genkeypair -keystore tlstest.jks -dname "CN=test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, \
- * C=Unknown" -keypass changeit -storepass changeit -keyalg RSA -alias localhost-ipv6 -ext \
- * SAN=dns:localhost,ip:0:0:0:0:0:0:0:1
- * ```
- */
- InputStream is = getClass().getResourceAsStream("/tlstest.p12")) {
- Preconditions.checkNotNull(is, "tlstest.p12 must be on the classpath");
- IOUtils.copy(is, os);
- }
-
- try (OutputStream osJKS = new FileOutputStream(_tlsStoreJKS);
- /*
- * Command to generate the tlstest.pkcs file (generate key pairs for both IPV4 and IPV6 addresses):
- * ```
- * keytool -genkeypair -storetype JKS -keystore tlstest.p12 -dname "CN=test, OU=Unknown, O=Unknown, \
- * L=Unknown, ST=Unknown, C=Unknown" -keypass changeit -storepass changeit -keyalg RSA \
- * -alias localhost-ipv4 -ext SAN=dns:localhost,ip:127.0.0.1
- *
- * keytool -genkeypair -storetype JKS -keystore tlstest.p12 -dname "CN=test, OU=Unknown, O=Unknown, \
- * L=Unknown, ST=Unknown, C=Unknown" -keypass changeit -storepass changeit -keyalg RSA \
- * -alias localhost-ipv6 -ext SAN=dns:localhost,ip:0:0:0:0:0:0:0:1
- * ```
- */
- InputStream isJKS = getClass().getResourceAsStream("/tlstest.jks")) {
- Preconditions.checkNotNull(isJKS, "tlstest.jks must be on the classpath");
- IOUtils.copy(isJKS, osJKS);
- }
- }
}
diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java
new file mode 100644
index 0000000..a6e3c5c
--- /dev/null
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java
@@ -0,0 +1,354 @@
+/**
+ * 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.pinot.integration.tests;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.Header;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.pinot.client.Connection;
+import org.apache.pinot.client.ConnectionFactory;
+import org.apache.pinot.client.JsonAsyncHttpPinotClientTransportFactory;
+import org.apache.pinot.common.utils.FileUploadDownloadClient;
+import org.apache.pinot.integration.tests.access.CertBasedTlsChannelAccessControlFactory;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.env.PinotConfiguration;
+import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.builder.TableNameBuilder;
+import org.apache.pinot.util.TestUtils;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.apache.pinot.integration.tests.BasicAuthTestUtils.AUTH_HEADER;
+import static org.apache.pinot.integration.tests.BasicAuthTestUtils.AUTH_TOKEN;
+
+
+public class TlsIntegrationTest extends BaseClusterIntegrationTest {
+ private static final String PASSWORD = "changeit";
+ private static final char[] PASSWORD_CHAR = PASSWORD.toCharArray();
+ private static final Header CLIENT_HEADER = new BasicHeader("Authorization", AUTH_TOKEN);
+
+ private static final int INTERNAL_CONTROLLER_PORT = DEFAULT_CONTROLLER_PORT + 1;
+ private static final int INTERNAL_BROKER_PORT = DEFAULT_BROKER_PORT + 1;
+ private static final String PKCS_12 = "PKCS12";
+ private static final String JKS = "JKS";
+
+ private final URL _tlsStoreEmptyPKCS12 = TlsIntegrationTest.class.getResource("/empty.p12");
+ private final URL _tlsStoreEmptyJKS = TlsIntegrationTest.class.getResource("/empty.jks");
+ private final URL _tlsStorePKCS12 = TlsIntegrationTest.class.getResource("/tlstest.p12");
+ private final URL _tlsStoreJKS = TlsIntegrationTest.class.getResource("/tlstest.jks");
+
+ @BeforeClass
+ public void setUp()
+ throws Exception {
+ TestUtils.ensureDirectoriesExistAndEmpty(_tempDir);
+
+ // Start Zookeeper
+ startZk();
+ // Start Pinot cluster
+ startKafka();
+ startController();
+ startBrokerHttps();
+ startServerHttps();
+
+ // Unpack the Avro files
+ List<File> avroFiles = unpackAvroData(_tempDir);
+
+ // Create and upload the schema and table config
+ addSchema(createSchema());
+ addTableConfig(createRealtimeTableConfig(avroFiles.get(0)));
+ addTableConfig(createOfflineTableConfig());
+
+ // Push data into Kafka
+ pushAvroIntoKafka(avroFiles);
+ waitForAllDocsLoaded(600_000L);
+
+ System.out.println("hello world!");
+
+ Thread.sleep(600000);
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void tearDown()
+ throws Exception {
+ dropRealtimeTable(getTableName());
+ stopServer();
+ stopBroker();
+ stopController();
+ stopKafka();
+ stopZk();
+ FileUtils.deleteDirectory(_tempDir);
+ }
+
+ @Override
+ public Map<String, Object> getDefaultControllerConfiguration() {
+ Map<String, Object> prop = super.getDefaultControllerConfiguration();
+ prop.put("controller.tls.keystore.path", _tlsStorePKCS12);
+ prop.put("controller.tls.keystore.password", PASSWORD);
+ prop.put("controller.tls.keystore.type", PKCS_12);
+ prop.put("controller.tls.truststore.path", _tlsStorePKCS12);
+ prop.put("controller.tls.truststore.password", PASSWORD);
+ prop.put("controller.tls.truststore.type", PKCS_12);
+
+// prop.put("controller.access.protocols", "https");
+// prop.put("controller.access.protocols.https.port", DEFAULT_CONTROLLER_PORT);
+ prop.put("controller.access.protocols", "https,internal");
+ prop.put("controller.access.protocols.https.port", DEFAULT_CONTROLLER_PORT);
+ prop.put("controller.access.protocols.https.tls.keystore.path", _tlsStoreJKS);
+ prop.put("controller.access.protocols.https.tls.keystore.type", JKS);
+ prop.put("controller.access.protocols.https.tls.truststore.path", _tlsStoreJKS);
+ prop.put("controller.access.protocols.https.tls.truststore.type", JKS);
+ prop.put("controller.access.protocols.internal.protocol", "https");
+ prop.put("controller.access.protocols.internal.port", INTERNAL_CONTROLLER_PORT);
+ prop.put("controller.access.protocols.internal.tls.client.auth.enabled", "true");
+
+ prop.put("controller.broker.protocol", "https");
+ prop.put("controller.broker.port.override", INTERNAL_BROKER_PORT);
+
+ // announce external only
+ prop.put("controller.vip.protocol", "https");
+ prop.put("controller.vip.port", DEFAULT_CONTROLLER_PORT);
+
+ prop.remove("controller.port");
+
+ return BasicAuthTestUtils.addControllerConfiguration(prop);
+ }
+
+ @Override
+ protected PinotConfiguration getDefaultBrokerConfiguration() {
+ Map<String, Object> prop = super.getDefaultBrokerConfiguration().toMap();
+ prop.put("pinot.broker.tls.keystore.path", _tlsStorePKCS12);
+ prop.put("pinot.broker.tls.keystore.password", PASSWORD);
+ prop.put("pinot.broker.tls.keystore.type", PKCS_12);
+ prop.put("pinot.broker.tls.truststore.path", _tlsStorePKCS12);
+ prop.put("pinot.broker.tls.truststore.password", PASSWORD);
+ prop.put("pinot.broker.tls.truststore.type", PKCS_12);
+
+// prop.put("pinot.broker.client.access.protocols", "https");
+// prop.put("pinot.broker.client.access.protocols.https.port", DEFAULT_BROKER_PORT);
+ prop.put("pinot.broker.client.access.protocols", "https,internal");
+ prop.put("pinot.broker.client.access.protocols.https.port", DEFAULT_BROKER_PORT);
+ prop.put("pinot.broker.client.access.protocols.https.tls.keystore.path", _tlsStoreJKS);
+ prop.put("pinot.broker.client.access.protocols.https.tls.keystore.type", JKS);
+ prop.put("pinot.broker.client.access.protocols.https.tls.truststore.path", _tlsStoreJKS);
+ prop.put("pinot.broker.client.access.protocols.https.tls.truststore.type", JKS);
+ prop.put("pinot.broker.client.access.protocols.internal.protocol", "https");
+ prop.put("pinot.broker.client.access.protocols.internal.port", INTERNAL_BROKER_PORT);
+ prop.put("pinot.broker.client.access.protocols.internal.tls.client.auth.enabled", "true");
+
+ prop.put("pinot.broker.nettytls.enabled", "true");
+
+ return BasicAuthTestUtils.addBrokerConfiguration(prop);
+ }
+
+ @Override
+ protected PinotConfiguration getDefaultServerConfiguration() {
+ Map<String, Object> prop = super.getDefaultServerConfiguration().toMap();
+ prop.put("pinot.server.tls.keystore.path", _tlsStorePKCS12);
+ prop.put("pinot.server.tls.keystore.password", PASSWORD);
+ prop.put("pinot.server.tls.keystore.type", PKCS_12);
+ prop.put("pinot.server.tls.truststore.path", _tlsStorePKCS12);
+ prop.put("pinot.server.tls.truststore.password", PASSWORD);
+ prop.put("pinot.server.tls.truststore.type", PKCS_12);
+ prop.put("pinot.server.tls.client.auth.enabled", "true");
+
+ prop.put("pinot.server.admin.access.control.factory.class",
+ CertBasedTlsChannelAccessControlFactory.class.getName());
+// prop.put("pinot.server.adminapi.access.protocols", "https");
+// prop.put("pinot.server.adminapi.access.protocols.https.port", "7443");
+ prop.put("pinot.server.adminapi.access.protocols", "internal");
+ prop.put("pinot.server.adminapi.access.protocols.internal.protocol", "https");
+ prop.put("pinot.server.adminapi.access.protocols.internal.port", "7443");
+ prop.put("pinot.server.netty.enabled", "false");
+ prop.put("pinot.server.nettytls.enabled", "true");
+ prop.put("pinot.server.nettytls.port", "8089");
+ prop.put("pinot.server.segment.uploader.protocol", "https");
+
+ return BasicAuthTestUtils.addServerConfiguration(prop);
+ }
+
+ @Override
+ protected boolean useLlc() {
+ return true;
+ }
+
+ @Override
+ protected void addSchema(Schema schema)
+ throws IOException {
+ PostMethod response =
+ sendMultipartPostRequest(_controllerRequestURLBuilder.forSchemaCreate(), schema.toSingleLineJsonString(),
+ AUTH_HEADER);
+ Assert.assertEquals(response.getStatusCode(), 200);
+ }
+
+ @Override
+ protected void addTableConfig(TableConfig tableConfig)
+ throws IOException {
+ sendPostRequest(_controllerRequestURLBuilder.forTableCreate(), tableConfig.toJsonString(), AUTH_HEADER);
+ }
+
+ @Override
+ protected Connection getPinotConnection() {
+ if (_pinotConnection == null) {
+ JsonAsyncHttpPinotClientTransportFactory factory = new JsonAsyncHttpPinotClientTransportFactory();
+ factory.setHeaders(AUTH_HEADER);
+ factory.setScheme(CommonConstants.HTTPS_PROTOCOL);
+ factory.setSslContext(FileUploadDownloadClient._defaultSSLContext);
+
+ _pinotConnection =
+ ConnectionFactory.fromZookeeper(getZkUrl() + "/" + getHelixClusterName(), factory.buildTransport());
+ }
+ return _pinotConnection;
+ }
+
+ @Override
+ protected void dropRealtimeTable(String tableName)
+ throws IOException {
+ sendDeleteRequest(
+ _controllerRequestURLBuilder.forTableDelete(TableNameBuilder.REALTIME.tableNameWithType(tableName)),
+ AUTH_HEADER);
+ }
+
+ @Test
+ public void testQueryControllerExternalTrustedServer()
+ throws Exception {
+ try (CloseableHttpClient client = makeClient(JKS, _tlsStoreJKS, _tlsStoreJKS)) {
+ HttpUriRequest request = new HttpGet("https://localhost:" + DEFAULT_CONTROLLER_PORT + "/tables");
+ request.addHeader(CLIENT_HEADER);
+
+ try (CloseableHttpResponse response = client.execute(request)) {
+ Assert.assertEquals(response.getStatusLine().getStatusCode(), 200);
+ String output = IOUtils.toString(response.getEntity().getContent());
+ System.out.println(output);
+ }
+ }
+ }
+
+ @Test
+ public void testQueryControllerExternalUntrustedServer()
+ throws Exception {
+ try (CloseableHttpClient client = makeClient(JKS, _tlsStoreJKS, _tlsStoreEmptyJKS)) {
+ HttpUriRequest request = new HttpGet("https://localhost:" + DEFAULT_CONTROLLER_PORT + "/tables");
+ request.addHeader(CLIENT_HEADER);
+
+ try {
+ client.execute(request);
+ Assert.fail("Must not allow connection to untrusted server");
+ } catch (IOException ignore) {
+ // this should fail
+ }
+ }
+ }
+
+ @Test
+ public void testQueryControllerInternalTrustedClient()
+ throws Exception {
+ try (CloseableHttpClient client = makeClient(PKCS_12, _tlsStorePKCS12, _tlsStorePKCS12)) {
+ HttpUriRequest request = new HttpGet("https://localhost:" + INTERNAL_CONTROLLER_PORT + "/tables");
+ request.addHeader(CLIENT_HEADER);
+
+ try (CloseableHttpResponse response = client.execute(request)) {
+ Assert.assertEquals(response.getStatusLine().getStatusCode(), 200);
+ String output = IOUtils.toString(response.getEntity().getContent());
+ System.out.println(output);
+ }
+ }
+ }
+
+ @Test
+ public void testQueryControllerInternalUntrustedClient()
+ throws Exception {
+ try (CloseableHttpClient client = makeClient(PKCS_12, _tlsStoreEmptyPKCS12, _tlsStorePKCS12)) {
+ HttpUriRequest request = new HttpGet("https://localhost:" + INTERNAL_CONTROLLER_PORT + "/tables");
+ request.addHeader(CLIENT_HEADER);
+
+ try {
+ client.execute(request);
+ Assert.fail("Must not allow connection from untrusted client");
+ } catch (IOException ignore) {
+ // this should fail
+ }
+ }
+ }
+
+ @Test
+ public void testQueryBrokerExternal()
+ throws Exception {
+ Assert.fail("not implemented yet");
+ }
+
+ @Test
+ public void testQueryBrokerInternal()
+ throws Exception {
+ Assert.fail("not implemented yet");
+ }
+
+ private static CloseableHttpClient makeClient(String keyStoreType, URL keyStoreUrl, URL trustStoreUrl) {
+ try {
+ SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
+ sslContextBuilder.setKeyStoreType(keyStoreType);
+ sslContextBuilder.loadKeyMaterial(keyStoreUrl, PASSWORD_CHAR, PASSWORD_CHAR);
+ sslContextBuilder.loadTrustMaterial(trustStoreUrl, PASSWORD_CHAR);
+ return HttpClientBuilder.create().setSSLContext(sslContextBuilder.build()).build();
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not create HTTPS client");
+ }
+ }
+
+ /*
+ * Command to generate the tlstest.jks file (generate key pairs for both IPV4 and IPV6 addresses):
+ * ```
+ * keytool -genkeypair -keystore tlstest.jks -dname "CN=test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, \
+ * C=Unknown" -keypass changeit -storepass changeit -keyalg RSA -alias localhost-ipv4 -ext \
+ * SAN=dns:localhost,ip:127.0.0.1
+ *
+ * keytool -genkeypair -keystore tlstest.jks -dname "CN=test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, \
+ * C=Unknown" -keypass changeit -storepass changeit -keyalg RSA -alias localhost-ipv6 -ext \
+ * SAN=dns:localhost,ip:0:0:0:0:0:0:0:1
+ * ```
+ */
+
+ /*
+ * Command to generate the tlstest.pkcs file (generate key pairs for both IPV4 and IPV6 addresses):
+ * ```
+ * keytool -genkeypair -storetype JKS -keystore tlstest.p12 -dname "CN=test, OU=Unknown, O=Unknown, \
+ * L=Unknown, ST=Unknown, C=Unknown" -keypass changeit -storepass changeit -keyalg RSA \
+ * -alias localhost-ipv4 -ext SAN=dns:localhost,ip:127.0.0.1
+ *
+ * keytool -genkeypair -storetype JKS -keystore tlstest.p12 -dname "CN=test, OU=Unknown, O=Unknown, \
+ * L=Unknown, ST=Unknown, C=Unknown" -keypass changeit -storepass changeit -keyalg RSA \
+ * -alias localhost-ipv6 -ext SAN=dns:localhost,ip:0:0:0:0:0:0:0:1
+ * ```
+ */
+}
diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java
index 99f9501..dd65e77 100644
--- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java
@@ -46,7 +46,8 @@ public class CertBasedTlsChannelAccessControlFactory implements AccessControlFac
private final Logger _logger = LoggerFactory.getLogger(CertBasedTlsChannelAccessControl.class);
private final Set<String> _aclPrincipalAllowlist = new HashSet<String>() {{
- add("CN=test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown");
+ add("CN=test-jks, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown");
+ add("CN=test-p12, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown");
}};
@Override
diff --git a/pinot-integration-tests/src/test/resources/empty.jks b/pinot-integration-tests/src/test/resources/empty.jks
new file mode 100644
index 0000000..c408465
Binary files /dev/null and b/pinot-integration-tests/src/test/resources/empty.jks differ
diff --git a/pinot-integration-tests/src/test/resources/empty.p12 b/pinot-integration-tests/src/test/resources/empty.p12
new file mode 100644
index 0000000..4f5baf1
Binary files /dev/null and b/pinot-integration-tests/src/test/resources/empty.p12 differ
diff --git a/pinot-integration-tests/src/test/resources/tlstest.jks b/pinot-integration-tests/src/test/resources/tlstest.jks
index 12f28e2..a43845f 100644
Binary files a/pinot-integration-tests/src/test/resources/tlstest.jks and b/pinot-integration-tests/src/test/resources/tlstest.jks differ
diff --git a/pinot-integration-tests/src/test/resources/tlstest.p12 b/pinot-integration-tests/src/test/resources/tlstest.p12
index 7750790..23c49e4 100644
Binary files a/pinot-integration-tests/src/test/resources/tlstest.p12 and b/pinot-integration-tests/src/test/resources/tlstest.p12 differ
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org