diff --git a/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java b/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java index 1521974c4cb..be234e7e625 100644 --- a/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java +++ b/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.common.util.Sampler; +import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.common.util.TransportType; import com.linecorp.armeria.server.TransientServiceOption; @@ -160,6 +161,11 @@ public Boolean useOpenSsl() { return true; } + @Override + public TlsEngineType tlsEngineType() { + return TlsEngineType.OPENSSL; + } + @Override public Boolean dumpOpenSslInfo() { return false; diff --git a/core/src/main/java/com/linecorp/armeria/common/Flags.java b/core/src/main/java/com/linecorp/armeria/common/Flags.java index 4d69683e86b..58e7041189b 100644 --- a/core/src/main/java/com/linecorp/armeria/common/Flags.java +++ b/core/src/main/java/com/linecorp/armeria/common/Flags.java @@ -53,6 +53,7 @@ import com.linecorp.armeria.common.util.Exceptions; import com.linecorp.armeria.common.util.Sampler; import com.linecorp.armeria.common.util.SystemInfo; +import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.common.util.TransportType; import com.linecorp.armeria.internal.common.FlagsLoaded; import com.linecorp.armeria.internal.common.util.SslContextUtil; @@ -107,7 +108,6 @@ public final class Flags { .sorted(Comparator.comparingInt(FlagsProvider::priority).reversed()) .collect(Collectors.toList()); flagsProviders.add(0, SystemPropertyFlagsProvider.INSTANCE); - flagsProviders.add(DefaultFlagsProvider.INSTANCE); FLAGS_PROVIDERS = ImmutableList.copyOf(flagsProviders); } @@ -190,7 +190,8 @@ private static boolean validateTransportType(TransportType transportType, String getValue(FlagsProvider::transportType, "transportType", TRANSPORT_TYPE_VALIDATOR); @Nullable - private static Boolean useOpenSsl; + private static TlsEngineType tlsEngineType; + @Nullable private static Boolean dumpOpenSslInfo; @@ -431,7 +432,6 @@ public static Sampler> verboseExceptionSampler() { * stack trace of the exceptions that are thrown frequently by Armeria. * * @see #verboseExceptionSampler() - * * @deprecated Use {@link #verboseExceptionSampler()} and * {@code -Dcom.linecorp.armeria.verboseExceptions=}. */ @@ -540,32 +540,67 @@ public static TransportType transportType() { * *

This flag is enabled by default for supported platforms. Specify the * {@code -Dcom.linecorp.armeria.useOpenSsl=false} JVM option to disable it. + * + * @deprecated Use {@link #tlsEngineType()} and {@code -Dcom.linecorp.armeria.tlsEngineType=openssl}. */ + @Deprecated public static boolean useOpenSsl() { - if (useOpenSsl != null) { - return useOpenSsl; + return tlsEngineType() == TlsEngineType.OPENSSL; + } + + /** + * Returns the {@link TlsEngineType} that will be used for processing TLS connections. + * + *

The default value of this flag is {@link TlsEngineType#OPENSSL}. + * Specify the {@code -Dcom.linecorp.armeria.tlsEngineType=} JVM option to override + * the default value. + */ + @UnstableApi + public static TlsEngineType tlsEngineType() { + if (tlsEngineType != null) { + return tlsEngineType; } - setUseOpenSslAndDumpOpenSslInfo(); - return useOpenSsl; + detectTlsEngineAndDumpOpenSslInfo(); + return tlsEngineType; } - private static void setUseOpenSslAndDumpOpenSslInfo() { - final boolean useOpenSsl = getValue(FlagsProvider::useOpenSsl, "useOpenSsl"); - if (!useOpenSsl) { - // OpenSSL explicitly disabled - Flags.useOpenSsl = false; - dumpOpenSslInfo = false; - return; + private static void detectTlsEngineAndDumpOpenSslInfo() { + + final Boolean useOpenSsl = getUserValue(FlagsProvider::useOpenSsl, "useOpenSsl", + ignored -> true); + final TlsEngineType tlsEngineTypeValue = getUserValue(FlagsProvider::tlsEngineType, + "tlsEngineType", ignored -> true); + + if (useOpenSsl != null && (useOpenSsl != (tlsEngineTypeValue == TlsEngineType.OPENSSL))) { + logger.warn("useOpenSsl({}) and tlsEngineType({}) are incompatible, tlsEngineType will be used", + useOpenSsl, tlsEngineTypeValue); + } + + TlsEngineType preferredTlsEngineType = null; + if (tlsEngineTypeValue != null) { + preferredTlsEngineType = tlsEngineTypeValue; + } else if (useOpenSsl != null) { + preferredTlsEngineType = useOpenSsl ? TlsEngineType.OPENSSL : TlsEngineType.JDK; + } + if (preferredTlsEngineType == TlsEngineType.OPENSSL) { + if (!OpenSsl.isAvailable()) { + final Throwable cause = Exceptions.peel(OpenSsl.unavailabilityCause()); + logger.info("OpenSSL not available: {}", cause.toString()); + preferredTlsEngineType = TlsEngineType.JDK; + } + } + if (preferredTlsEngineType == null) { + preferredTlsEngineType = OpenSsl.isAvailable() ? TlsEngineType.OPENSSL : TlsEngineType.JDK; } - if (!OpenSsl.isAvailable()) { - final Throwable cause = Exceptions.peel(OpenSsl.unavailabilityCause()); - logger.info("OpenSSL not available: {}", cause.toString()); - Flags.useOpenSsl = false; + tlsEngineType = preferredTlsEngineType; + + if (tlsEngineType != TlsEngineType.OPENSSL) { dumpOpenSslInfo = false; + logger.info("Using TLS engine: {}", tlsEngineType); return; } - Flags.useOpenSsl = true; - logger.info("Using OpenSSL: {}, 0x{}", OpenSsl.versionString(), + + logger.info("Using Tls engine: OpenSSL {}, 0x{}", OpenSsl.versionString(), Long.toHexString(OpenSsl.version() & 0xFFFFFFFFL)); dumpOpenSslInfo = getValue(FlagsProvider::dumpOpenSslInfo, "dumpOpenSslInfo"); if (dumpOpenSslInfo) { @@ -590,14 +625,14 @@ private static void setUseOpenSslAndDumpOpenSslInfo() { *

This flag is disabled by default. Specify the {@code -Dcom.linecorp.armeria.dumpOpenSslInfo=true} JVM * option to enable it. * - *

If {@link #useOpenSsl()} returns {@code false}, this also returns {@code false} no matter you - * specified the JVM option. + *

If {@link #tlsEngineType()} does not return {@link TlsEngineType#OPENSSL}, this also returns + * {@code false} no matter what the specified JVM option is. */ public static boolean dumpOpenSslInfo() { if (dumpOpenSslInfo != null) { return dumpOpenSslInfo; } - setUseOpenSslAndDumpOpenSslInfo(); + detectTlsEngineAndDumpOpenSslInfo(); return dumpOpenSslInfo; } @@ -1236,7 +1271,6 @@ public static String dnsCacheSpec() { * to override the default value. * * @see ExceptionVerbosity - * * @deprecated Use {@link LoggingService} or log exceptions using * {@link ServerBuilder#errorHandler(ServerErrorHandler)}. */ @@ -1589,24 +1623,37 @@ private static T getValue(Function method, Strin private static T getValue(Function method, String flagName, Predicate validator) { + final T t = getUserValue(method, flagName, validator); + if (t != null) { + return t; + } + + return method.apply(DefaultFlagsProvider.INSTANCE); + } + + @Nullable + private static T getUserValue(Function method, String flagName, + Predicate validator) { for (FlagsProvider provider : FLAGS_PROVIDERS) { try { final T value = method.apply(provider); if (value == null) { continue; } + if (!validator.test(value)) { logger.warn("{}: {} ({}, validation failed)", flagName, value, provider.name()); continue; } + logger.info("{}: {} ({})", flagName, value, provider.name()); return value; } catch (Exception ex) { logger.warn("{}: ({}, {})", flagName, provider.name(), ex.getMessage()); } } - // Should never reach here because DefaultFlagsProvider always returns a normal value. - throw new Error(); + + return null; } private Flags() {} diff --git a/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java b/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java index 4c44b53c783..e2b606fc1c2 100644 --- a/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java +++ b/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java @@ -42,6 +42,7 @@ import com.linecorp.armeria.common.util.Exceptions; import com.linecorp.armeria.common.util.Sampler; import com.linecorp.armeria.common.util.SystemInfo; +import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.common.util.TransportType; import com.linecorp.armeria.server.HttpService; import com.linecorp.armeria.server.ServerBuilder; @@ -198,12 +199,27 @@ default TransportType transportType() { * *

This flag is enabled by default for supported platforms. Specify the * {@code -Dcom.linecorp.armeria.useOpenSsl=false} JVM option to disable it. + * + * @deprecated Use {@link #tlsEngineType()} and {@code -Dcom.linecorp.armeria.tlsEngineType=openssl}. */ @Nullable + @Deprecated default Boolean useOpenSsl() { return null; } + /** + * Returns the {@link TlsEngineType} that will be used for processing TLS connections. + * + *

The default value of this flag is "openssl", which means the {@link TlsEngineType#OPENSSL} will + * be used. Specify the {@code -Dcom.linecorp.armeria.tlsEngineType=} JVM option to override + * the default.

+ */ + @Nullable + default TlsEngineType tlsEngineType() { + return null; + } + /** * Returns whether information about the OpenSSL environment should be dumped when first starting the * application, including supported ciphers. @@ -211,8 +227,8 @@ default Boolean useOpenSsl() { *

This flag is disabled by default. Specify the {@code -Dcom.linecorp.armeria.dumpOpenSslInfo=true} JVM * option to enable it. * - *

If {@link #useOpenSsl()} returns {@code false}, this also returns {@code false} no matter you - * specified the JVM option. + *

If {@link #tlsEngineType()} does not return {@link TlsEngineType#OPENSSL}, this also returns + * {@code false} no matter what the specified JVM option is. */ @Nullable default Boolean dumpOpenSslInfo() { diff --git a/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java b/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java index 5e27f0e4261..d1b3411fbd1 100644 --- a/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java +++ b/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java @@ -39,6 +39,7 @@ import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.common.util.InetAddressPredicates; import com.linecorp.armeria.common.util.Sampler; +import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.common.util.TransportType; import com.linecorp.armeria.server.TransientServiceOption; @@ -148,6 +149,23 @@ public Boolean useOpenSsl() { return getBoolean("useOpenSsl"); } + @Override + public TlsEngineType tlsEngineType() { + final String strTlsEngineType = getNormalized("tlsEngineType"); + if (strTlsEngineType == null) { + return null; + } + switch (strTlsEngineType) { + case "jdk": + return TlsEngineType.JDK; + case "openssl": + return TlsEngineType.OPENSSL; + default: + throw new IllegalArgumentException( + String.format("%s isn't one of 'jdk' or 'openssl'", strTlsEngineType)); + } + } + @Override public Boolean dumpOpenSslInfo() { return getBoolean("dumpOpenSslInfo"); diff --git a/core/src/main/java/com/linecorp/armeria/common/util/TlsEngineType.java b/core/src/main/java/com/linecorp/armeria/common/util/TlsEngineType.java new file mode 100644 index 00000000000..a979eff0d94 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/common/util/TlsEngineType.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 LINE Corporation + * + * LINE Corporation 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: + * + * https://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 com.linecorp.armeria.common.util; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +import io.netty.handler.ssl.SslProvider; + +/** + * Tls engine types. + */ +@UnstableApi +public enum TlsEngineType { + /** + * JDK's default implementation. + */ + JDK(SslProvider.JDK), + /** + * OpenSSL-based implementation. + */ + OPENSSL(SslProvider.OPENSSL); + + private final SslProvider sslProvider; + + TlsEngineType(SslProvider sslProvider) { + this.sslProvider = sslProvider; + } + + /** + * Returns the {@link SslProvider} corresponding to this {@link TlsEngineType}. + */ + public SslProvider sslProvider() { + return sslProvider; + } +} diff --git a/core/src/main/java/com/linecorp/armeria/internal/common/util/SslContextUtil.java b/core/src/main/java/com/linecorp/armeria/internal/common/util/SslContextUtil.java index 487a0c728b0..f78ca18c29d 100644 --- a/core/src/main/java/com/linecorp/armeria/internal/common/util/SslContextUtil.java +++ b/core/src/main/java/com/linecorp/armeria/internal/common/util/SslContextUtil.java @@ -103,7 +103,7 @@ public static SslContext createSslContext( return MinifiedBouncyCastleProvider.call(() -> { final SslContextBuilder builder = builderSupplier.get(); - final SslProvider provider = Flags.useOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK; + final SslProvider provider = Flags.tlsEngineType().sslProvider(); builder.sslProvider(provider); final Set supportedProtocols = supportedProtocols(builder); @@ -147,7 +147,7 @@ public static SslContext createSslContext( "You must specify at least one cipher suite."); if (forceHttp1) { - // Skip validation + // Skip validation } else { validateHttp2Ciphers(ciphers, tlsAllowUnsafeCiphers); } diff --git a/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java b/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java index ed91d00fbc6..beefebaff05 100644 --- a/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java @@ -90,6 +90,7 @@ import com.linecorp.armeria.common.util.EventLoopGroups; import com.linecorp.armeria.common.util.SystemInfo; import com.linecorp.armeria.common.util.ThreadFactories; +import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.internal.common.BuiltInDependencyInjector; import com.linecorp.armeria.internal.common.ReflectiveDependencyInjector; import com.linecorp.armeria.internal.common.RequestContextUtil; @@ -2181,7 +2182,7 @@ private DefaultServerConfig buildServerConfig(List serverPorts) { ports = ImmutableList.of(new ServerPort(0, HTTP)); } } else { - if (!Flags.useOpenSsl() && !SystemInfo.jettyAlpnOptionalOrAvailable()) { + if (Flags.tlsEngineType() != TlsEngineType.OPENSSL && !SystemInfo.jettyAlpnOptionalOrAvailable()) { throw new IllegalStateException( "TLS configured but this is Java 8 and neither OpenSSL nor Jetty ALPN could be " + "detected. To use TLS with Armeria, you must either use Java 9+, enable OpenSSL, " + diff --git a/core/src/test/java/com/linecorp/armeria/common/FlagsTest.java b/core/src/test/java/com/linecorp/armeria/common/FlagsTest.java index f7106d8267f..2750562faf2 100644 --- a/core/src/test/java/com/linecorp/armeria/common/FlagsTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/FlagsTest.java @@ -47,6 +47,7 @@ import com.linecorp.armeria.common.util.Exceptions; import com.linecorp.armeria.common.util.Sampler; +import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.common.util.TransportType; import com.linecorp.armeria.server.TransientServiceOption; @@ -85,9 +86,9 @@ void epollAvailableOnLinux() { void openSslAvailable() { assumeThat(osName.startsWith("linux") || osName.startsWith("windows") || osName.startsWith("mac") || osName.startsWith("osx")).isTrue(); - assumeThat(System.getProperty("com.linecorp.armeria.useOpenSsl")).isNull(); + assumeThat(System.getProperty("com.linecorp.armeria.tlsEngineType")).isNull(); - assertThat(Flags.useOpenSsl()).isTrue(); + assertThat(Flags.tlsEngineType()).isEqualTo(TlsEngineType.OPENSSL); assertThat(OpenSsl.isAvailable()).isTrue(); } @@ -107,10 +108,24 @@ void dumpOpenSslInfoDoNotThrowStackOverFlowError() throws Throwable { final MethodHandle dumpOpenSslInfoMethodHandle = lookup.findStatic(flags, "dumpOpenSslInfo", MethodType.methodType(boolean.class)); - // // Call Flags.dumpOpenSslInfo(); + // Call Flags.dumpOpenSslInfo(); assertThat(dumpOpenSslInfoMethodHandle.invoke()).isSameAs(Boolean.TRUE); } + @Test + void defaultTlsEngineType() { + assumeThat(System.getProperty("com.linecorp.armeria.tlsEngineType")).isNull(); + + assertThat(Flags.tlsEngineType()).isEqualTo(TlsEngineType.OPENSSL); + } + + @Test + @SetSystemProperty(key = "com.linecorp.armeria.useOpenSsl", value = "false") + @SetSystemProperty(key = "com.linecorp.armeria.tlsEngineType", value = "OPENSSL") + void tlsEngineTypeIsUsedWhenIncompatibleWithUseOpenSsl() { + assertThat(Flags.tlsEngineType()).isEqualTo(TlsEngineType.OPENSSL); + } + @Test void defaultRequestContextStorageProvider() throws Throwable { assumeThat(System.getProperty("com.linecorp.armeria.requestContextStorageProvider")).isNull(); @@ -252,16 +267,18 @@ void invalidSystemPropertyRequestContextLeakDetectionSampler() throws Exception void testApiConsistencyBetweenFlagsAndFlagsProvider() { //Check method consistency between Flags and FlagsProvider excluding deprecated methods final Set flagsApis = Arrays.stream(Flags.class.getMethods()) - .filter(m -> !m.isAnnotationPresent(Deprecated.class)) - .map(Method::getName) - .collect(Collectors.toSet()); + .filter(m -> !m.isAnnotationPresent(Deprecated.class)) + .map(Method::getName) + .collect(Collectors.toSet()); flagsApis.removeAll(Arrays.stream(Object.class.getMethods()) - .map(Method::getName) - .collect(toImmutableSet())); + .map(Method::getName) + .collect(toImmutableSet())); final Set armeriaOptionsProviderApis = Arrays.stream(FlagsProvider.class.getMethods()) - .map(Method::getName) - .collect(Collectors.toSet()); + .filter(m -> !m.isAnnotationPresent( + Deprecated.class)) + .map(Method::getName) + .collect(Collectors.toSet()); final Set knownIgnoreMethods = ImmutableSet.of("priority", "name"); armeriaOptionsProviderApis.removeAll(knownIgnoreMethods); diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/util/SslContextUtilTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/util/SslContextUtilTest.java index 2562933a43c..7228197957d 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/util/SslContextUtilTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/util/SslContextUtilTest.java @@ -43,7 +43,6 @@ class SslContextUtilTest { @Test void openSsl() { - assumeThat(Flags.useOpenSsl()).isTrue(); final Set supportedProtocols = SslContextUtil.supportedProtocols( SslContextBuilder.forClient().sslProvider(SslProvider.OPENSSL)); assertThat(supportedProtocols).contains("TLSv1.2", "TLSv1.3"); @@ -88,7 +87,7 @@ private static String getBadCipher() { try { final SslContext sslCtx = MinifiedBouncyCastleProvider.call(() -> { final SslContextBuilder builder = SslContextBuilder.forClient(); - final SslProvider provider = Flags.useOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK; + final SslProvider provider = Flags.tlsEngineType().sslProvider(); builder.sslProvider(provider); builder.protocols("TLSv1.2").ciphers(ImmutableList.of(cipher)); diff --git a/it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java b/it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java index 4c1b669a9fa..9377ab9c6cb 100644 --- a/it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java +++ b/it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java @@ -22,10 +22,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; @@ -38,6 +34,7 @@ import com.linecorp.armeria.common.util.Exceptions; import com.linecorp.armeria.common.util.InetAddressPredicates; +import com.linecorp.armeria.common.util.TlsEngineType; import io.micrometer.core.instrument.Metrics; @@ -57,7 +54,7 @@ void reloadFlags() throws ClassNotFoundException { @Test void overrideDefaultFlagsProvider() throws Throwable { - assertFlags("useOpenSsl").isEqualTo(false); + assertFlags("tlsEngineType").isNotEqualTo(TlsEngineType.OPENSSL); assertFlags("numCommonBlockingTaskThreads").isEqualTo(100); } @@ -127,6 +124,14 @@ void twoRequestContextStorageProvidersAreProvidedAndInvalidFQCNisSpecify() throw assertThat(actual).isEqualTo(Custom1RequestContextStorageProvider.class.getSimpleName()); } + @Test + @SetSystemProperty(key = "com.linecorp.armeria.tlsEngineType", value = "jdk") + void overrideDefaultTlsEngineType() throws Throwable { + final Method method = flags.getDeclaredMethod("tlsEngineType"); + final String actual = method.invoke(flags).toString(); + assertThat(actual).isEqualTo(TlsEngineType.JDK.toString()); + } + @Test void twoRequestContextStorageProvidersAreProvidedButNoFQCNisSpecify() throws Throwable { assumeThat(System.getProperty("com.linecorp.armeria.requestContextStorageProvider")).isNull(); @@ -155,11 +160,8 @@ void testDistributionStatisticConfig() { } private ObjectAssert assertFlags(String flagsMethod) throws Throwable { - final Lookup lookup = MethodHandles.publicLookup(); - final MethodHandle method = - lookup.findStatic(flags, flagsMethod, MethodType.methodType( - Flags.class.getMethod(flagsMethod).getReturnType())); - return assertThat(method.invoke()); + final Method method = flags.getDeclaredMethod(flagsMethod); + return assertThat(method.invoke(null)); } private static class FlagsClassLoader extends ClassLoader {