diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java index 5a345ff2..9dec3de6 100644 --- a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java @@ -79,6 +79,8 @@ public ForwardedDevServerBuildItem prepareDevService( final DevServerConfig devServerConfig = resolvedConfig.devServer(); liveReload.setContextObject(QuinoaConfig.class, resolvedConfig); final String configuredDevServerHost = devServerConfig.host(); + final boolean configuredTls = devServerConfig.tls(); + final boolean configuredTlsAllowInsecure = devServerConfig.tlsAllowInsecure(); final PackageManagerRunner packageManagerRunner = installedPackageManager.getPackageManager(); final String checkPath = resolvedConfig.devServer().checkPath().orElse(null); if (devService != null) { @@ -92,7 +94,9 @@ public ForwardedDevServerBuildItem prepareDevService( } LOG.debug("Quinoa config did not change; no need to restart."); devServices.produce(devService.toBuildItem()); - final String resolvedDevServerHost = PackageManagerRunner.isDevServerUp(devServerConfig.host(), + final String resolvedDevServerHost = PackageManagerRunner.isDevServerUp(devServerConfig.tls(), + devServerConfig.tlsAllowInsecure(), + devServerConfig.host(), devServerConfig.port().get(), checkPath); return new ForwardedDevServerBuildItem(resolvedDevServerHost, devServerConfig.port().get()); @@ -118,7 +122,8 @@ public ForwardedDevServerBuildItem prepareDevService( if (!devServerConfig.managed()) { // No need to start the dev-service it is not managed by Quinoa // We just check that it is up - final String resolvedHostIPAddress = PackageManagerRunner.isDevServerUp(configuredDevServerHost, port, checkPath); + final String resolvedHostIPAddress = PackageManagerRunner.isDevServerUp(configuredTls, configuredTlsAllowInsecure, + configuredDevServerHost, port, checkPath); if (resolvedHostIPAddress != null) { return new ForwardedDevServerBuildItem(resolvedHostIPAddress, port); } else { @@ -135,7 +140,8 @@ public ForwardedDevServerBuildItem prepareDevService( final AtomicReference dev = new AtomicReference<>(); PackageManagerRunner.DevServer devServer = null; try { - devServer = packageManagerRunner.dev(consoleInstalled, loggingSetup, configuredDevServerHost, + devServer = packageManagerRunner.dev(consoleInstalled, loggingSetup, configuredTls, configuredTlsAllowInsecure, + configuredDevServerHost, port, checkPath, checkTimeout); diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/SslUtil.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/SslUtil.java new file mode 100644 index 00000000..f33b144d --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/SslUtil.java @@ -0,0 +1,55 @@ +package io.quarkiverse.quinoa.deployment; + +import javax.net.ssl.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +public class SslUtil { + private final static X509ExtendedTrustManager NON_VALIDATING_TRUST_MANAGER = new X509ExtendedTrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + public static SSLContext createNonValidatingSslContext() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { NON_VALIDATING_TRUST_MANAGER }, new SecureRandom()); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } +} diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/DevServerConfig.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/DevServerConfig.java index 5740b1e4..1bb75901 100644 --- a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/DevServerConfig.java +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/DevServerConfig.java @@ -40,6 +40,18 @@ public interface DevServerConfig { @WithDefault("localhost") String host(); + /** + * Protocol of the server to forward requests to. + */ + @WithDefault("false") + boolean tls(); + + /** + * Protocol of the server to forward requests to. + */ + @WithDefault("false") + boolean tlsAllowInsecure(); + /** * After start, Quinoa wait for the external dev server. * by sending GET requests to this path waiting for a 200 status. @@ -100,6 +112,12 @@ static boolean isEqual(DevServerConfig d1, DevServerConfig d2) { if (!Objects.equals(d1.host(), d2.host())) { return false; } + if (!Objects.equals(d1.tls(), d2.tls())) { + return false; + } + if (!Objects.equals(d1.tlsAllowInsecure(), d2.tlsAllowInsecure())) { + return false; + } if (!Objects.equals(d1.checkPath(), d2.checkPath())) { return false; } diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/delegate/DevServerConfigDelegate.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/delegate/DevServerConfigDelegate.java index 1fafb07a..b22185d0 100644 --- a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/delegate/DevServerConfigDelegate.java +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/config/delegate/DevServerConfigDelegate.java @@ -31,6 +31,16 @@ public String host() { return delegate.host(); } + @Override + public boolean tls() { + return delegate.tls(); + } + + @Override + public boolean tlsAllowInsecure() { + return delegate.tlsAllowInsecure(); + } + @Override public Optional checkPath() { return delegate.checkPath(); diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerRunner.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerRunner.java index 822ca96f..6504ae86 100644 --- a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerRunner.java +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerRunner.java @@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import io.quarkiverse.quinoa.deployment.SslUtil; import org.jboss.logging.Logger; import io.quarkiverse.quinoa.deployment.config.PackageManagerCommandConfig; @@ -33,6 +34,10 @@ import io.quarkus.dev.console.QuarkusConsole; import io.quarkus.runtime.LaunchMode; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + public class PackageManagerRunner { private static final Logger LOG = Logger.getLogger(PackageManagerRunner.class); public static final Predicate DEV_PROCESS_THREAD_PREDICATE = thread -> thread.getName() @@ -134,7 +139,8 @@ private static void killDescendants(ProcessHandle process, boolean force) { } public DevServer dev(Optional consoleInstalled, LoggingSetupBuildItem loggingSetup, - String devServerHost, int devServerPort, String checkPath, int checkTimeout) { + boolean tls, boolean tlsAllowInsecure, String devServerHost, int devServerPort, String checkPath, + int checkTimeout) { final PackageManager.Command dev = packageManager.dev(); LOG.infof("Running Quinoa package manager live coding as a dev service: %s", dev.commandWithArguments); StartupLogCompressor logCompressor = new StartupLogCompressor( @@ -155,7 +161,7 @@ public void run() { String ipAddress = null; try { int i = 0; - while ((ipAddress = isDevServerUp(devServerHost, devServerPort, checkPath)) == null) { + while ((ipAddress = isDevServerUp(tls, tlsAllowInsecure, devServerHost, devServerPort, checkPath)) == null) { if (++i >= checkTimeout / 500) { stopDev(p); throw new RuntimeException( @@ -269,7 +275,7 @@ public void run() { } } - public static String isDevServerUp(String host, int port, String path) { + public static String isDevServerUp(boolean tls, boolean tlsAllowInsecure, String host, int port, String path) { if (path == null) { return host; } @@ -280,8 +286,24 @@ public static String isDevServerUp(String host, int port, String path) { try { final String hostAddress = address.getHostAddress(); final String ipAddress = address instanceof Inet6Address ? "[" + hostAddress + "]" : hostAddress; - URL url = new URL(String.format("http://%s:%d%s", ipAddress, port, normalizedPath)); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + URL url = new URL(String.format("%s://%s:%d%s", tls ? "https" : "http", ipAddress, port, normalizedPath)); + HttpURLConnection connection; + if (tls) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) url.openConnection(); + if (tlsAllowInsecure) { + httpsConnection.setSSLSocketFactory(SslUtil.createNonValidatingSslContext().getSocketFactory()); + httpsConnection.setHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } + connection = httpsConnection; + } else { + connection = (HttpURLConnection) url.openConnection(); + } + connection.setRequestMethod("GET"); connection.setConnectTimeout(2000); connection.setReadTimeout(2000);