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

Replace SimpleHttpClient (okhttp based) by JdkHttpClient (HttpURLConnection based) #1061

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion aws-resources/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ dependencies {
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")

implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.squareup.okhttp3:okhttp")
compileOnly("com.squareup.okhttp3:okhttp")

testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")

testImplementation("com.linecorp.armeria:armeria-junit5")
testRuntimeOnly("org.bouncycastle:bcpkix-jdk15on")
testRuntimeOnly("com.squareup.okhttp3:okhttp")
testImplementation("com.google.guava:guava")
testImplementation("org.skyscreamer:jsonassert")
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private static String fetchHostname(URL hostnameUrl, String token) {

// Generic HTTP fetch function for IMDS.
private static String fetchString(String httpMethod, URL url, String token, boolean includeTtl) {
SimpleHttpClient client = new SimpleHttpClient();
JdkHttpClient client = new JdkHttpClient();
Map<String, String> headers = new HashMap<>();

if (includeTtl) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public static Resource get() {
}

private static Resource buildResource() {
return buildResource(System.getenv(), new SimpleHttpClient());
return buildResource(System.getenv(), new JdkHttpClient());
}

// Visible for testing
static Resource buildResource(Map<String, String> sysEnv, SimpleHttpClient httpClient) {
static Resource buildResource(Map<String, String> sysEnv, JdkHttpClient httpClient) {
// Note: If V4 is set V3 is set as well, so check V4 first.
String ecsMetadataUrl =
sysEnv.getOrDefault(ECS_METADATA_KEY_V4, sysEnv.getOrDefault(ECS_METADATA_KEY_V3, ""));
Expand All @@ -64,8 +64,7 @@ static Resource buildResource(Map<String, String> sysEnv, SimpleHttpClient httpC
return Resource.empty();
}

static void fetchMetadata(
SimpleHttpClient httpClient, String url, AttributesBuilder attrBuilders) {
static void fetchMetadata(JdkHttpClient httpClient, String url, AttributesBuilder attrBuilders) {
String json = httpClient.fetchString("GET", url, Collections.emptyMap(), null);
if (json.isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ public static Resource get() {
}

private static Resource buildResource() {
return buildResource(new SimpleHttpClient(), new DockerHelper(), K8S_TOKEN_PATH, K8S_CERT_PATH);
return buildResource(new JdkHttpClient(), new DockerHelper(), K8S_TOKEN_PATH, K8S_CERT_PATH);
}

// Visible for testing
static Resource buildResource(
SimpleHttpClient httpClient,
JdkHttpClient httpClient,
DockerHelper dockerHelper,
String k8sTokenPath,
String k8sKeystorePath) {
Expand All @@ -83,7 +83,7 @@ static Resource buildResource(
}

private static boolean isEks(
String k8sTokenPath, String k8sKeystorePath, SimpleHttpClient httpClient) {
String k8sTokenPath, String k8sKeystorePath, JdkHttpClient httpClient) {
if (!isK8s(k8sTokenPath, k8sKeystorePath)) {
logger.log(Level.FINE, "Not running on k8s.");
return false;
Expand All @@ -104,7 +104,7 @@ private static boolean isK8s(String k8sTokenPath, String k8sKeystorePath) {
return k8sTokeyFile.exists() && k8sKeystoreFile.exists();
}

private static String getClusterName(SimpleHttpClient httpClient) {
private static String getClusterName(JdkHttpClient httpClient) {
Map<String, String> requestProperties = new HashMap<>();
requestProperties.put("Authorization", getK8sCredHeader());
String json =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.aws.resource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;

/** A simple HTTP client based on JDK HttpURLConnection. Not meant for high throughput. */
final class JdkHttpClient {

private static final Logger logger = Logger.getLogger(JdkHttpClient.class.getName());

private static final int TIMEOUT = 2000;

/** Fetch a string from a remote server. */
public String fetchString(
String httpMethod, String urlStr, Map<String, String> headers, @Nullable String certPath) {

try {
// create URL from string
URL url = new URL(urlStr);
// create connection
URLConnection connection = url.openConnection();
// https
if (connection instanceof HttpsURLConnection) {
// cast
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
// check CA cert path is available
if (certPath != null) {
// create trust manager
X509TrustManager trustManager = SslSocketFactoryBuilder.createTrustManager(certPath);
// socket factory
SSLSocketFactory socketFactory =
SslSocketFactoryBuilder.createSocketFactory(trustManager);
if (socketFactory != null) {
// update connection
httpsConnection.setSSLSocketFactory(socketFactory);
}
}
// process request
return processRequest(httpsConnection, httpMethod, urlStr, headers);
}
// http
if (connection instanceof HttpURLConnection) {
// cast
HttpURLConnection httpConnection = (HttpURLConnection) connection;
// process request
return processRequest(httpConnection, httpMethod, urlStr, headers);
}
// not http
logger.log(Level.FINE, "JdkHttpClient only HTTP/HTTPS connections are supported.");
} catch (MalformedURLException e) {
logger.log(Level.FINE, "JdkHttpClient invalid URL.", e);
} catch (IOException e) {
logger.log(Level.FINE, "JdkHttpClient fetch string failed.", e);
}
return "";
}

private static String processRequest(
HttpURLConnection httpConnection,
String httpMethod,
String urlStr,
Map<String, String> headers)
throws IOException {
// set method
httpConnection.setRequestMethod(httpMethod);
// set headers
headers.forEach(httpConnection::setRequestProperty);
// timeouts
httpConnection.setConnectTimeout(TIMEOUT);
httpConnection.setReadTimeout(TIMEOUT);
// connect
httpConnection.connect();
try {
// status code
int responseCode = httpConnection.getResponseCode();
if (responseCode != 200) {
logger.log(
Level.FINE,
"Error response from "
+ urlStr
+ " code ("
+ responseCode
+ ") text "
+ httpConnection.getResponseMessage());
return "";
}
// read response
try (InputStream inputStream = httpConnection.getInputStream()) {
// store read data in byte array
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// read all bytes
int b;
while ((b = inputStream.read()) != -1) {
outputStream.write(b);
}
// result
return outputStream.toString("UTF-8");
}
}
} finally {
// disconnect, no need for persistent connections
httpConnection.disconnect();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,13 @@

package io.opentelemetry.contrib.aws.resource;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand Down Expand Up @@ -47,9 +39,8 @@ public String fetchString(
.readTimeout(TIMEOUT);

if (urlStr.startsWith("https") && certPath != null) {
KeyStore keyStore = getKeystoreForTrustedCert(certPath);
X509TrustManager trustManager = buildTrustManager(keyStore);
SSLSocketFactory socketFactory = buildSslSocketFactory(trustManager);
X509TrustManager trustManager = SslSocketFactoryBuilder.createTrustManager(certPath);
SSLSocketFactory socketFactory = SslSocketFactoryBuilder.createSocketFactory(trustManager);
if (socketFactory != null) {
clientBuilder.sslSocketFactory(socketFactory, trustManager);
}
Expand Down Expand Up @@ -89,57 +80,4 @@ public String fetchString(

return "";
}

@Nullable
private static X509TrustManager buildTrustManager(@Nullable KeyStore keyStore) {
if (keyStore == null) {
return null;
}
try {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return (X509TrustManager) tmf.getTrustManagers()[0];
} catch (Exception e) {
logger.log(Level.WARNING, "Build SslSocketFactory for K8s restful client exception.", e);
return null;
}
}

@Nullable
private static SSLSocketFactory buildSslSocketFactory(@Nullable TrustManager trustManager) {
if (trustManager == null) {
return null;
}
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] {trustManager}, null);
return context.getSocketFactory();

} catch (Exception e) {
logger.log(Level.WARNING, "Build SslSocketFactory for K8s restful client exception.", e);
}
return null;
}

@Nullable
private static KeyStore getKeystoreForTrustedCert(String certPath) {
try (FileInputStream fis = new FileInputStream(certPath)) {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(fis);

int i = 0;
for (Certificate certificate : certificates) {
trustStore.setCertificateEntry("cert_" + i, certificate);
i++;
}
return trustStore;
} catch (Exception e) {
logger.log(Level.WARNING, "Cannot load KeyStore from " + certPath);
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.aws.resource;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

class SslSocketFactoryBuilder {

private static final Logger logger = Logger.getLogger(SslSocketFactoryBuilder.class.getName());

private SslSocketFactoryBuilder() {}

@Nullable
private static KeyStore getKeystoreForTrustedCert(String certPath) {
try (FileInputStream fis = new FileInputStream(certPath)) {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(fis);

int i = 0;
for (Certificate certificate : certificates) {
trustStore.setCertificateEntry("cert_" + i, certificate);
i++;
}
return trustStore;
} catch (Exception e) {
logger.log(Level.WARNING, "Cannot load KeyStore from " + certPath);
return null;
}
}

@Nullable
static X509TrustManager createTrustManager(@Nullable String certPath) {
if (certPath == null) {
return null;
}
try {
// create keystore
KeyStore keyStore = getKeystoreForTrustedCert(certPath);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return (X509TrustManager) tmf.getTrustManagers()[0];
} catch (Exception e) {
logger.log(Level.WARNING, "Build SslSocketFactory for K8s restful client exception.", e);
return null;
}
}

@Nullable
static SSLSocketFactory createSocketFactory(@Nullable TrustManager trustManager) {
if (trustManager == null) {
return null;
}
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] {trustManager}, null);
return context.getSocketFactory();

} catch (Exception e) {
logger.log(Level.WARNING, "Build SslSocketFactory for K8s restful client exception.", e);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class EcsResourceTest {
private static final String ECS_METADATA_KEY_V4 = "ECS_CONTAINER_METADATA_URI_V4";
private static final String ECS_METADATA_KEY_V3 = "ECS_CONTAINER_METADATA_URI";

@Mock private SimpleHttpClient mockHttpClient;
@Mock private JdkHttpClient mockHttpClient;

@Test
void testCreateAttributesV3() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class EksResourceTest {

@Mock private DockerHelper mockDockerHelper;

@Mock private SimpleHttpClient httpClient;
@Mock private JdkHttpClient httpClient;

@Test
void testEks(@TempDir File tempFolder) throws IOException {
Expand Down
Loading
Loading