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 <apuc...@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-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to