Skip to content

Commit ef2a423

Browse files
authored
Merge branch 'master' into andrea.marziali/websocket-propagate
2 parents f67f0a8 + 4a5461c commit ef2a423

File tree

50 files changed

+1120
-468
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1120
-468
lines changed

.circleci/config.continue.yml.j2

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ instrumentation_modules: &instrumentation_modules "dd-java-agent/instrumentation
3636
debugger_modules: &debugger_modules "dd-java-agent/agent-debugger|dd-java-agent/agent-bootstrap|dd-java-agent/agent-builder|internal-api|communication|dd-trace-core"
3737
profiling_modules: &profiling_modules "dd-java-agent/agent-profiling"
3838

39-
default_system_tests_commit: &default_system_tests_commit 69a5e874384dd256e2e3f42fc1c95807a67efbe6
39+
default_system_tests_commit: &default_system_tests_commit 1ef00a34ad1f83ae999887e510ef1ea1c27b151b
4040

4141
parameters:
4242
nightly:
@@ -414,7 +414,7 @@ jobs:
414414
name: Check Project
415415
command: >-
416416
MAVEN_OPTS="-Xms64M -Xmx256M"
417-
GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx3G -Xms2G -XX:ErrorFile=/tmp/hs_err_pid%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp'"
417+
GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx2G -Xms2G -XX:ErrorFile=/tmp/hs_err_pid%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp'"
418418
./gradlew
419419
<< parameters.gradleTarget >>
420420
-PskipTests
@@ -1105,7 +1105,7 @@ build_test_jobs: &build_test_jobs
11051105
requires:
11061106
- ok_to_test
11071107
name: check_inst
1108-
parallelism: 4
1108+
parallelism: 5
11091109
gradleTarget: ":instrumentationCheck"
11101110
cacheType: inst
11111111
triggeredBy: *instrumentation_modules

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import datadog.trace.api.config.UsmConfig;
3232
import datadog.trace.api.gateway.RequestContextSlot;
3333
import datadog.trace.api.gateway.SubscriptionService;
34+
import datadog.trace.api.git.EmbeddedGitInfoBuilder;
35+
import datadog.trace.api.git.GitInfoProvider;
3436
import datadog.trace.api.profiling.ProfilingEnablement;
3537
import datadog.trace.api.scopemanager.ScopeListener;
3638
import datadog.trace.bootstrap.benchmark.StaticEventLogger;
@@ -225,6 +227,12 @@ public static void start(
225227
}
226228
}
227229

230+
// Enable automatic fetching of git tags from datadog_git.properties only if CI Visibility is
231+
// not enabled
232+
if (!ciVisibilityEnabled) {
233+
GitInfoProvider.INSTANCE.registerGitInfoBuilder(new EmbeddedGitInfoBuilder());
234+
}
235+
228236
boolean dataJobsEnabled = isFeatureEnabled(AgentFeature.DATA_JOBS);
229237
if (dataJobsEnabled) {
230238
log.info("Data Jobs Monitoring enabled, enabling spark integrations");

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package datadog.trace.civisibility.domain;
22

33
import static datadog.json.JsonMapper.toJson;
4-
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpanWithoutScope;
55
import static datadog.trace.civisibility.Constants.CI_VISIBILITY_INSTRUMENTATION_NAME;
66

77
import datadog.trace.api.Config;
@@ -116,7 +116,7 @@ public TestImpl(
116116

117117
span = spanBuilder.start();
118118

119-
activateSpan(span);
119+
activateSpanWithoutScope(span);
120120

121121
span.setSpanType(InternalSpanTypes.TEST);
122122
span.setTag(Tags.SPAN_KIND, Tags.SPAN_KIND_TEST);

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestSuiteImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package datadog.trace.civisibility.domain;
22

33
import static datadog.json.JsonMapper.toJson;
4-
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpanWithoutScope;
55
import static datadog.trace.civisibility.Constants.CI_VISIBILITY_INSTRUMENTATION_NAME;
66

77
import datadog.trace.api.Config;
@@ -130,7 +130,7 @@ public TestSuiteImpl(
130130
testDecorator.afterStart(span);
131131

132132
if (!parallelized) {
133-
activateSpan(span);
133+
activateSpanWithoutScope(span);
134134
}
135135

136136
metricCollector.add(CiVisibilityCountMetric.EVENT_CREATED, 1, instrumentation, EventType.SUITE);

dd-java-agent/appsec/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ ext {
8282
'com.datadog.appsec.config.AppSecFeatures.ApiSecurity',
8383
'com.datadog.appsec.config.AppSecFeatures.AutoUserInstrum',
8484
'com.datadog.appsec.event.ReplaceableEventProducerService',
85+
'com.datadog.appsec.api.security.ApiSecuritySampler.NoOp',
8586
]
8687
excludedClassesBranchCoverage = [
8788
'com.datadog.appsec.gateway.GatewayBridge',

dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.datadog.appsec;
22

3-
import com.datadog.appsec.api.security.ApiSecurityRequestSampler;
3+
import com.datadog.appsec.api.security.ApiSecuritySampler;
4+
import com.datadog.appsec.api.security.ApiSecuritySamplerImpl;
5+
import com.datadog.appsec.api.security.AppSecSpanPostProcessor;
46
import com.datadog.appsec.blocking.BlockingServiceImpl;
57
import com.datadog.appsec.config.AppSecConfigService;
68
import com.datadog.appsec.config.AppSecConfigServiceImpl;
@@ -21,6 +23,7 @@
2123
import datadog.trace.api.telemetry.ProductChange;
2224
import datadog.trace.api.telemetry.ProductChangeCollector;
2325
import datadog.trace.bootstrap.ActiveSubsystems;
26+
import datadog.trace.bootstrap.instrumentation.api.SpanPostProcessor;
2427
import java.util.Collections;
2528
import java.util.HashMap;
2629
import java.util.List;
@@ -66,7 +69,17 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s
6669
EventDispatcher eventDispatcher = new EventDispatcher();
6770
REPLACEABLE_EVENT_PRODUCER.replaceEventProducerService(eventDispatcher);
6871

69-
ApiSecurityRequestSampler requestSampler = new ApiSecurityRequestSampler(config);
72+
ApiSecuritySampler requestSampler;
73+
if (Config.get().isApiSecurityEnabled()) {
74+
requestSampler = new ApiSecuritySamplerImpl();
75+
// When DD_API_SECURITY_ENABLED=true, ths post-processor is set even when AppSec is inactive.
76+
// This should be low overhead since the post-processor exits early if there's no AppSec
77+
// context.
78+
SpanPostProcessor.Holder.INSTANCE =
79+
new AppSecSpanPostProcessor(requestSampler, REPLACEABLE_EVENT_PRODUCER);
80+
} else {
81+
requestSampler = new ApiSecuritySampler.NoOp();
82+
}
7083

7184
ConfigurationPoller configurationPoller = sco.configurationPoller(config);
7285
// may throw and abort startup

dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/ApiSecurityRequestSampler.java

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.datadog.appsec.api.security;
2+
3+
import com.datadog.appsec.gateway.AppSecRequestContext;
4+
import javax.annotation.Nonnull;
5+
6+
public interface ApiSecuritySampler {
7+
/**
8+
* Prepare a request context for later sampling decision. This method should be called at request
9+
* end, and is thread-safe. If a request can potentially be sampled, this method will return true.
10+
* If this method returns true, the caller MUST call {@link #releaseOne()} once the context is not
11+
* needed anymore.
12+
*/
13+
boolean preSampleRequest(final @Nonnull AppSecRequestContext ctx);
14+
15+
/** Get the final sampling decision. This method is NOT required to be thread-safe. */
16+
boolean sampleRequest(AppSecRequestContext ctx);
17+
18+
/** Release one permit for the sampler. This must be called after processing a span. */
19+
void releaseOne();
20+
21+
final class NoOp implements ApiSecuritySampler {
22+
@Override
23+
public boolean preSampleRequest(@Nonnull AppSecRequestContext ctx) {
24+
return false;
25+
}
26+
27+
@Override
28+
public boolean sampleRequest(AppSecRequestContext ctx) {
29+
return false;
30+
}
31+
32+
@Override
33+
public void releaseOne() {}
34+
}
35+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.datadog.appsec.api.security;
2+
3+
import com.datadog.appsec.gateway.AppSecRequestContext;
4+
import datadog.trace.api.Config;
5+
import datadog.trace.api.time.SystemTimeSource;
6+
import datadog.trace.api.time.TimeSource;
7+
import java.util.Deque;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.concurrent.ConcurrentLinkedDeque;
10+
import java.util.concurrent.Semaphore;
11+
import javax.annotation.Nonnull;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
public class ApiSecuritySamplerImpl implements ApiSecuritySampler {
16+
17+
private static final Logger log = LoggerFactory.getLogger(ApiSecuritySamplerImpl.class);
18+
19+
/**
20+
* A maximum number of request contexts we'll keep open past the end of request at any given time.
21+
* This will avoid excessive memory usage in case of a high number of concurrent requests, and
22+
* should also prevent memory leaks.
23+
*/
24+
private static final int MAX_POST_PROCESSING_TASKS = 4;
25+
/** Maximum number of entries in the access map. */
26+
private static final int MAX_SIZE = 4096;
27+
/** Mapping from endpoint hash to last access timestamp in millis. */
28+
private final ConcurrentHashMap<Long, Long> accessMap;
29+
/** Deque of endpoint hashes ordered by access time. Oldest is always first. */
30+
private final Deque<Long> accessDeque;
31+
32+
private final long expirationTimeInMs;
33+
private final int capacity;
34+
private final TimeSource timeSource;
35+
private final Semaphore counter = new Semaphore(MAX_POST_PROCESSING_TASKS);
36+
37+
public ApiSecuritySamplerImpl() {
38+
this(
39+
MAX_SIZE,
40+
(long) (Config.get().getApiSecuritySampleDelay() * 1_000),
41+
SystemTimeSource.INSTANCE);
42+
}
43+
44+
public ApiSecuritySamplerImpl(
45+
int capacity, long expirationTimeInMs, @Nonnull TimeSource timeSource) {
46+
this.capacity = capacity;
47+
this.expirationTimeInMs = expirationTimeInMs;
48+
this.accessMap = new ConcurrentHashMap<>();
49+
this.accessDeque = new ConcurrentLinkedDeque<>();
50+
this.timeSource = timeSource;
51+
}
52+
53+
@Override
54+
public boolean preSampleRequest(final @Nonnull AppSecRequestContext ctx) {
55+
final String route = ctx.getRoute();
56+
if (route == null) {
57+
return false;
58+
}
59+
final String method = ctx.getMethod();
60+
if (method == null) {
61+
return false;
62+
}
63+
final int statusCode = ctx.getResponseStatus();
64+
if (statusCode <= 0) {
65+
return false;
66+
}
67+
long hash = computeApiHash(route, method, statusCode);
68+
ctx.setApiSecurityEndpointHash(hash);
69+
if (!isApiAccessExpired(hash)) {
70+
return false;
71+
}
72+
if (counter.tryAcquire()) {
73+
log.debug("API security sampling is required for this request (presampled)");
74+
ctx.setKeepOpenForApiSecurityPostProcessing(true);
75+
return true;
76+
}
77+
return false;
78+
}
79+
80+
/** Get the final sampling decision. This method is NOT thread-safe. */
81+
@Override
82+
public boolean sampleRequest(AppSecRequestContext ctx) {
83+
if (ctx == null) {
84+
return false;
85+
}
86+
final Long hash = ctx.getApiSecurityEndpointHash();
87+
if (hash == null) {
88+
// This should never happen, it should have been short-circuited before.
89+
return false;
90+
}
91+
return updateApiAccessIfExpired(hash);
92+
}
93+
94+
@Override
95+
public void releaseOne() {
96+
counter.release();
97+
}
98+
99+
private boolean updateApiAccessIfExpired(final long hash) {
100+
final long currentTime = timeSource.getCurrentTimeMillis();
101+
102+
Long lastAccess = accessMap.get(hash);
103+
if (lastAccess != null && currentTime - lastAccess < expirationTimeInMs) {
104+
return false;
105+
}
106+
107+
if (accessMap.put(hash, currentTime) == null) {
108+
accessDeque.addLast(hash);
109+
// If we added a new entry, we perform purging.
110+
cleanupExpiredEntries(currentTime);
111+
} else {
112+
// This is now the most recently accessed entry.
113+
accessDeque.remove(hash);
114+
accessDeque.addLast(hash);
115+
}
116+
117+
return true;
118+
}
119+
120+
private boolean isApiAccessExpired(final long hash) {
121+
final long currentTime = timeSource.getCurrentTimeMillis();
122+
final Long lastAccess = accessMap.get(hash);
123+
return lastAccess == null || currentTime - lastAccess >= expirationTimeInMs;
124+
}
125+
126+
private void cleanupExpiredEntries(final long currentTime) {
127+
// Purge all expired entries.
128+
while (!accessDeque.isEmpty()) {
129+
final Long oldestHash = accessDeque.peekFirst();
130+
if (oldestHash == null) {
131+
// Should never happen
132+
continue;
133+
}
134+
135+
final Long lastAccessTime = accessMap.get(oldestHash);
136+
if (lastAccessTime == null) {
137+
// Should never happen
138+
continue;
139+
}
140+
141+
if (currentTime - lastAccessTime < expirationTimeInMs) {
142+
// The oldest hash is up-to-date, so stop here.
143+
break;
144+
}
145+
146+
accessDeque.pollFirst();
147+
accessMap.remove(oldestHash);
148+
}
149+
150+
// If we went over capacity, remove the oldest entries until we are within the limit.
151+
// This should never be more than 1.
152+
final int toRemove = accessMap.size() - this.capacity;
153+
for (int i = 0; i < toRemove; i++) {
154+
Long oldestHash = accessDeque.pollFirst();
155+
if (oldestHash != null) {
156+
accessMap.remove(oldestHash);
157+
}
158+
}
159+
}
160+
161+
private long computeApiHash(final String route, final String method, final int statusCode) {
162+
long result = 17;
163+
result = 31 * result + route.hashCode();
164+
result = 31 * result + method.hashCode();
165+
result = 31 * result + statusCode;
166+
return result;
167+
}
168+
}

0 commit comments

Comments
 (0)