Skip to content
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 communication/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ dependencies {
api(project(":remote-config:remote-config-api"))
implementation(project(":remote-config:remote-config-core"))
implementation(project(":internal-api"))
implementation(project(":utils:container-utils"))
// ContainerInfo is part of the OkHttpUtils API
api(project(":utils:container-utils"))
implementation(project(":utils:filesystem-utils"))
implementation(project(":utils:socket-utils"))
implementation(project(":utils:version-utils"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static datadog.communication.ddagent.TracerVersion.TRACER_VERSION;
import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP;

import datadog.common.container.ContainerInfo;
import datadog.communication.http.OkHttpUtils;
import datadog.communication.http.SocketUtils;
import datadog.metrics.api.Monitoring;
Expand Down Expand Up @@ -80,7 +79,7 @@ public void createRemaining(Config config) {
forceClearTextHttpForIntakeClient = config.isForceClearTextHttpForIntakeClient();

if (agentUrl == null) {
agentUrl = parseAgentUrl(config);
agentUrl = OkHttpUtils.parseAgentUrl(config);
if (agentUrl == null) {
throw new IllegalArgumentException("Bad agent URL: " + config.getAgentUrl());
}
Expand Down Expand Up @@ -147,15 +146,6 @@ public void resume() {
}
}

private static HttpUrl parseAgentUrl(Config config) {
String agentUrl = config.getAgentUrl();
if (agentUrl.startsWith("unix:")) {
// provide placeholder agent URL, in practice we'll be tunnelling over UDS
agentUrl = "http://" + config.getAgentHost() + ":" + config.getAgentPort();
}
return HttpUrl.parse(agentUrl);
}

public ConfigurationPoller configurationPoller(Config config) {
if (configurationPoller == null && config.isRemoteConfigEnabled()) {
configurationPoller = createPoller(config);
Expand All @@ -164,8 +154,6 @@ public ConfigurationPoller configurationPoller(Config config) {
}

private ConfigurationPoller createPoller(Config config) {
String containerId = ContainerInfo.get().getContainerId();
String entityId = ContainerInfo.getEntityId();
Supplier<String> configUrlSupplier;
String remoteConfigUrl = config.getFinalRemoteConfigUrl();
if (remoteConfigUrl != null) {
Expand All @@ -175,7 +163,7 @@ private ConfigurationPoller createPoller(Config config) {
configUrlSupplier = new RetryConfigUrlSupplier(this, config);
}
return new DefaultConfigurationPoller(
config, TRACER_VERSION, containerId, entityId, configUrlSupplier, agentHttpClient);
config, TRACER_VERSION, configUrlSupplier, agentHttpClient);
}

// for testing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package datadog.communication.http;

import static datadog.communication.http.SocketUtils.discoverApmSocket;
import static datadog.trace.api.AgentUrls.toHttpUrl;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

Expand Down Expand Up @@ -48,7 +49,8 @@ public final class OkHttpUtils {
private static final String DATADOG_META_LANG_INTERPRETER_VENDOR =
"Datadog-Meta-Lang-Interpreter-Vendor";
public static final String DATADOG_CONTAINER_ID = "Datadog-Container-ID";
private static final String DATADOG_ENTITY_ID = "Datadog-Entity-ID";
static final String DATADOG_ENTITY_ID = "Datadog-Entity-ID";
static final String DATADOG_EXTERNAL_ENV = "Datadog-External-Env";
public static final String DATADOG_CONTAINER_TAGS_HASH = "Datadog-Container-Tags-Hash";

private static final String DD_API_KEY = "DD-API-KEY";
Expand Down Expand Up @@ -238,8 +240,48 @@ private static OkHttpClient buildHttpClient(
return client;
}

/** Prepares a request to the local Datadog Agent. */
public static Request.Builder prepareRequest(final HttpUrl url, Map<String, String> headers) {
return prepareRequest(url, headers, ContainerInfo.get(), true);
}

/**
* Prepares a request to the local Datadog Agent, or directly to the Datadog intake when {@code
* agentless}. Origin detection headers are only added when {@code url} targets the local Datadog
* Agent, so a user-configured endpoint never receives them.
*/
public static Request.Builder prepareRequest(
final HttpUrl url,
final Map<String, String> headers,
final Config config,
final boolean agentless) {
return prepareRequest(url, headers, config, agentless, ContainerInfo.get());
}

static Request.Builder prepareRequest(
final HttpUrl url,
final Map<String, String> headers,
final Config config,
final boolean agentless,
final ContainerInfo containerInfo) {
final boolean toAgent = !agentless && config.isAgentUrl(url.toString());
Request.Builder builder = prepareRequest(url, headers, containerInfo, toAgent);

final String apiKey = config.getApiKey();
if (agentless && apiKey != null) {
// we only add the api key header if we know we're doing agentless. No point in adding it to
// other agent-based requests since we know the datadog-agent isn't going to make use of it.
builder = builder.addHeader(DD_API_KEY, apiKey);
}

return builder;
}

static Request.Builder prepareRequest(
final HttpUrl url,
final Map<String, String> headers,
final ContainerInfo containerInfo,
final boolean toAgent) {
final Request.Builder builder =
new Request.Builder()
.url(url)
Expand All @@ -248,13 +290,8 @@ public static Request.Builder prepareRequest(final HttpUrl url, Map<String, Stri
.addHeader(DATADOG_META_LANG_INTERPRETER, JAVA_VM_NAME)
.addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, JAVA_VM_VENDOR);

final String containerId = ContainerInfo.get().getContainerId();
final String entityId = ContainerInfo.getEntityId();
if (containerId != null) {
builder.addHeader(DATADOG_CONTAINER_ID, containerId);
}
if (entityId != null) {
builder.addHeader(DATADOG_ENTITY_ID, entityId);
if (toAgent) {
addOriginHeaders(builder, containerInfo);
}

for (Map.Entry<String, String> e : headers.entrySet()) {
Expand All @@ -264,23 +301,46 @@ public static Request.Builder prepareRequest(final HttpUrl url, Map<String, Stri
return builder;
}

public static Request.Builder prepareRequest(
final HttpUrl url,
final Map<String, String> headers,
final Config config,
final boolean agentless) {
Request.Builder builder = prepareRequest(url, headers);
/**
* Adds the origin detection headers ({@code Datadog-Container-ID}, {@code Datadog-Entity-ID} and
* {@code Datadog-External-Env}) the Datadog Agent uses to resolve the container the request comes
* from, using the values detected by {@link ContainerInfo}. Only the local Datadog Agent uses
* them, so they must not be added to requests sent anywhere else.
*/
public static Request.Builder addOriginHeaders(final Request.Builder builder) {
return addOriginHeaders(builder, ContainerInfo.get());
}

final String apiKey = config.getApiKey();
if (agentless && apiKey != null) {
// we only add the api key header if we know we're doing agentless. No point in adding it to
// other agent-based requests since we know the datadog-agent isn't going to make use of it.
builder = builder.addHeader(DD_API_KEY, apiKey);
/**
* Adds the origin detection headers using the values detected by {@code containerInfo}. See
* {@link #addOriginHeaders(Request.Builder)}.
*/
public static Request.Builder addOriginHeaders(
final Request.Builder builder, final ContainerInfo containerInfo) {
final String containerId = containerInfo.getContainerId();
if (containerId != null) {
builder.addHeader(DATADOG_CONTAINER_ID, containerId);
}
final String entityId = containerInfo.getEntityId();
if (entityId != null) {
builder.addHeader(DATADOG_ENTITY_ID, entityId);
}
final String externalEnv = containerInfo.getExternalEnv();
if (externalEnv != null) {
builder.addHeader(DATADOG_EXTERNAL_ENV, externalEnv);
}

return builder;
}

/** Returns the HTTP URL of the local Datadog Agent, or {@code null} if it cannot be parsed. */
public static @Nullable HttpUrl parseAgentUrl(final Config config) {
final String agentUrl = config.getAgentUrl();
if (agentUrl == null) {
return null;
}
return HttpUrl.parse(toHttpUrl(agentUrl, config.getAgentHost(), config.getAgentPort()));
}

public static RequestBody msgpackRequestBodyOf(List<ByteBuffer> buffers) {
return new ByteBufferRequestBody(buffers);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package datadog.communication.http;

import static datadog.communication.http.OkHttpUtils.DATADOG_CONTAINER_ID;
import static datadog.communication.http.OkHttpUtils.DATADOG_ENTITY_ID;
import static datadog.communication.http.OkHttpUtils.DATADOG_EXTERNAL_ENV;
import static datadog.communication.http.OkHttpUtils.addOriginHeaders;
import static datadog.communication.http.OkHttpUtils.prepareRequest;
import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import datadog.common.container.ContainerInfo;
import datadog.trace.api.Config;
import datadog.trace.test.junit.utils.config.WithConfig;
import datadog.trace.test.util.DDJavaSpecification;
import okhttp3.HttpUrl;
import okhttp3.Request;
import org.junit.jupiter.api.Test;

class OkHttpUtilsPrepareRequestTest extends DDJavaSpecification {

private static final HttpUrl URL = HttpUrl.get("http://localhost:8126/v0.4/traces");
private static final String EXTERNAL_ENV =
"it-false,cn-nginx-webserver,pu-75a2b6d5-3949-4afb-ad0d-92ff0674e759";

@Test
void addsOriginHeadersWhenSet() {
ContainerInfo containerInfo = containerInfo("containerId", "entityId", EXTERNAL_ENV);

Request request = prepareRequest(URL, emptyMap(), containerInfo, true).get().build();

assertEquals("containerId", request.header(DATADOG_CONTAINER_ID));
assertEquals("entityId", request.header(DATADOG_ENTITY_ID));
assertEquals(EXTERNAL_ENV, request.header(DATADOG_EXTERNAL_ENV));
}

@Test
void addsExternalEnvHeaderWithoutContainerOrEntityId() {
// the external env is the origin detection fallback when the container is not identifiable
ContainerInfo containerInfo = containerInfo(null, null, EXTERNAL_ENV);

Request request = prepareRequest(URL, emptyMap(), containerInfo, true).get().build();

assertEquals(EXTERNAL_ENV, request.header(DATADOG_EXTERNAL_ENV));
assertNull(request.header(DATADOG_CONTAINER_ID));
assertNull(request.header(DATADOG_ENTITY_ID));
}

@Test
void omitsExternalEnvHeaderWhenUnset() {
ContainerInfo containerInfo = containerInfo(null, null, null);

Request request = prepareRequest(URL, emptyMap(), containerInfo, true).get().build();

assertNull(request.header(DATADOG_EXTERNAL_ENV));
}

@Test
void omitsOriginHeadersWhenNotSentToAgent() {
ContainerInfo containerInfo = containerInfo("containerId", "entityId", EXTERNAL_ENV);

Request request = prepareRequest(URL, emptyMap(), containerInfo, false).get().build();

assertNull(request.header(DATADOG_CONTAINER_ID));
assertNull(request.header(DATADOG_ENTITY_ID));
assertNull(request.header(DATADOG_EXTERNAL_ENV));
}

@Test
void addOriginHeadersOmitsUnsetHeaders() {
ContainerInfo containerInfo = containerInfo(null, null, null);

Request request = addOriginHeaders(new Request.Builder().url(URL), containerInfo).build();

assertEquals(0, request.headers().size());
}

@Test
@WithConfig(key = "trace.agent.url", value = "http://dd-agent:8126")
void addsOriginHeadersWhenNotAgentlessAndSentToAgent() {
ContainerInfo containerInfo = containerInfo("containerId", "entityId", EXTERNAL_ENV);

Request request =
prepareRequest(
HttpUrl.get("http://dd-agent:8126/profiling/v1/input"),
emptyMap(),
Config.get(),
false,
containerInfo)
.get()
.build();

assertEquals("containerId", request.header(DATADOG_CONTAINER_ID));
assertEquals("entityId", request.header(DATADOG_ENTITY_ID));
assertEquals(EXTERNAL_ENV, request.header(DATADOG_EXTERNAL_ENV));
}

@Test
@WithConfig(key = "trace.agent.url", value = "http://dd-agent:8126")
void omitsOriginHeadersWhenNotAgentlessButNotSentToAgent() {
// e.g. DD_PROFILING_URL targeting an external endpoint without DD_PROFILING_AGENTLESS
ContainerInfo containerInfo = containerInfo("containerId", "entityId", EXTERNAL_ENV);

Request request =
prepareRequest(
HttpUrl.get("https://profiles.example.com/api/v2/profile"),
emptyMap(),
Config.get(),
false,
containerInfo)
.get()
.build();

assertNull(request.header(DATADOG_CONTAINER_ID));
assertNull(request.header(DATADOG_ENTITY_ID));
assertNull(request.header(DATADOG_EXTERNAL_ENV));
}

@Test
@WithConfig(key = "trace.agent.url", value = "http://dd-agent:8126")
void omitsOriginHeadersWhenAgentless() {
ContainerInfo containerInfo = containerInfo("containerId", "entityId", EXTERNAL_ENV);

Request request =
prepareRequest(
HttpUrl.get("http://dd-agent:8126/profiling/v1/input"),
emptyMap(),
Config.get(),
true,
containerInfo)
.get()
.build();

assertNull(request.header(DATADOG_CONTAINER_ID));
assertNull(request.header(DATADOG_ENTITY_ID));
assertNull(request.header(DATADOG_EXTERNAL_ENV));
}

// inherited public ContainerInfo fields would shadow parameters with the same names
private static ContainerInfo containerInfo(
String detectedContainerId, String detectedEntityId, String detectedExternalEnv) {
return new ContainerInfo() {
@Override
public String getContainerId() {
return detectedContainerId;
}

@Override
public String getEntityId() {
return detectedEntityId;
}

@Override
public String getExternalEnv() {
return detectedExternalEnv;
}
};
}
}
Loading
Loading