Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Friendly exception for cipher suite issue #2053

Merged
merged 15 commits into from
Jan 25, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,36 @@
package com.microsoft.applicationinsights.agent.internal.common;

import com.microsoft.applicationinsights.agent.internal.configuration.DefaultEndpoints;
import io.netty.handler.ssl.SslHandshakeTimeoutException;
import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;

public class NetworkFriendlyExceptions {
public static final List<String> EXPECTED_CIPHERS =
Arrays.asList(
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256");

// returns true if the exception was "handled" and the caller should not log it
public static boolean logSpecialOneTimeFriendlyException(
Throwable error, String url, AtomicBoolean alreadySeen, Logger logger) {
// Handle SSL cert exceptions
SSLHandshakeException sslException = getCausedByOfType(error, SSLHandshakeException.class);
if (error instanceof SslHandshakeTimeoutException) {
return false;
}
if (sslException != null) {
if (!alreadySeen.getAndSet(true)) {
logger.error(getSslFriendlyMessage(url));
Expand All @@ -49,6 +65,36 @@ public static boolean logSpecialOneTimeFriendlyException(
// e.g. wrong host address or cannot reach address due to network issues...
return false;
}

IOException ioException = getCausedByOfType(error, IOException.class);
if (ioException != null) {
boolean foundCiphers = true;
try {
foundCiphers = hasExpectedCiphers();
} catch (NoSuchAlgorithmException e) {
logger.debug(e.getMessage(), e);
return false;
}
if (!foundCiphers) {
if (!alreadySeen.getAndSet(true)) {
logger.error(getCipherFriendlyMessage(url));
}
} else {
return false;
}
return true;
}
kryalama marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

private static boolean hasExpectedCiphers() throws NoSuchAlgorithmException {
SSLSocketFactory socketFactory = SSLContext.getDefault().getSocketFactory();
List<String> cipherSuitesFromJvm = Arrays.asList(socketFactory.getSupportedCipherSuites());
for (String cipher : EXPECTED_CIPHERS) {
if (cipherSuitesFromJvm.contains(cipher)) {
return true;
}
}
return false;
}

Expand All @@ -67,19 +113,42 @@ private static <T extends Exception> T getCausedByOfType(Throwable throwable, Cl

private static String getSslFriendlyMessage(String url) {
return FriendlyException.populateFriendlyMessage(
getSslFriendlyExceptionBanner(url),
getSslFriendlyExceptionAction(url),
"Unable to find valid certification path to requested target.",
getSslFriendlyExceptionAction(url),
getFriendlyExceptionBanner(url),
"This message is only logged the first time it occurs after startup.");
}

private static String getCipherFriendlyMessage(String url) {
return FriendlyException.populateFriendlyMessage(
"Probable root cause may be : missing cipher suites which are expected by the requested target.",
getCipherFriendlyExceptionAction(url),
getFriendlyExceptionBanner(url),
"This message is only logged the first time it occurs after startup.");
}

private static String getSslFriendlyExceptionBanner(String url) {
if (url.equals(DefaultEndpoints.LIVE_ENDPOINT)) {
private static String getFriendlyExceptionBanner(String url) {
if (url.contains(DefaultEndpoints.LIVE_ENDPOINT)) {
return "ApplicationInsights Java Agent failed to connect to Live metric end point.";
}
return "ApplicationInsights Java Agent failed to send telemetry data.";
}

private static String getCipherFriendlyExceptionAction(String url) {
StringBuilder actionBuilder = new StringBuilder();
actionBuilder
.append(
"The Application Insights Java agent detects that you do not have any of the following cipher suites that are supported by the endpoint it connects to: "
kryalama marked this conversation as resolved.
Show resolved Hide resolved
+ url)
.append("\n");
for (String missingCipher : EXPECTED_CIPHERS) {
actionBuilder.append(missingCipher).append("\n");
}
actionBuilder.append(
"Learn more about troubleshooting this network issue related to cipher suites here: https://go.microsoft.com/fwlink/?linkid=2185426");
return actionBuilder.toString();
}

private static String getSslFriendlyExceptionAction(String url) {
String customJavaKeyStorePath = getCustomJavaKeystorePath();
if (customJavaKeyStorePath != null) {
Expand Down