Skip to content

Commit 88f4eaf

Browse files
Partial IAST opt-out when AppSec is enabled (DataDog#6621)
Initial version of IAST opt-out with AppSec enabled
1 parent d41fb9c commit 88f4eaf

37 files changed

Lines changed: 618 additions & 91 deletions

File tree

‎benchmark/load/petclinic/benchmark.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
"JAVA_OPTS": "-javaagent:${TRACER} -Ddd.appsec.enabled=true"
3131
}
3232
},
33+
"appsec_no_iast": {
34+
"env": {
35+
"VARIANT": "appsec",
36+
"JAVA_OPTS": "-javaagent:${TRACER} -Ddd.appsec.enabled=true -Ddd.iast.enabled=false"
37+
}
38+
},
3339
"iast": {
3440
"env": {
3541
"VARIANT": "iast",

‎benchmark/startup/petclinic/benchmark.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
"JAVA_OPTS": "-javaagent:${TRACER} -Ddd.benchmark.enabled=true -Ddd.benchmark.output.dir=${OUTPUT_DIR}/appsec -Ddd.appsec.enabled=true"
2525
}
2626
},
27+
"appsec_no_iast": {
28+
"env": {
29+
"VARIANT": "appsec",
30+
"JAVA_OPTS": "-javaagent:${TRACER} -Ddd.benchmark.enabled=true -Ddd.benchmark.output.dir=${OUTPUT_DIR}/appsec_no_iast -Ddd.appsec.enabled=true -Ddd.iast.enabled=false"
31+
}
32+
},
2733
"iast": {
2834
"env": {
2935
"VARIANT": "iast",

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public boolean isEnabledByDefault() {
138138
private static boolean appSecFullyDisabled;
139139
private static boolean remoteConfigEnabled = true;
140140
private static boolean iastEnabled = false;
141+
private static boolean iastFullyDisabled;
141142
private static boolean cwsEnabled = false;
142143
private static boolean ciVisibilityEnabled = false;
143144
private static boolean usmEnabled = false;
@@ -206,10 +207,11 @@ public static void start(final Instrumentation inst, final URL agentJarURL, Stri
206207

207208
jmxFetchEnabled = isFeatureEnabled(AgentFeature.JMXFETCH);
208209
profilingEnabled = isFeatureEnabled(AgentFeature.PROFILING);
209-
iastEnabled = isFeatureEnabled(AgentFeature.IAST);
210210
usmEnabled = isFeatureEnabled(AgentFeature.USM);
211211
appSecEnabled = isFeatureEnabled(AgentFeature.APPSEC);
212-
appSecFullyDisabled = isAppSecFullyDisabled();
212+
appSecFullyDisabled = isFullyDisabled(AgentFeature.APPSEC);
213+
iastEnabled = isFeatureEnabled(AgentFeature.IAST);
214+
iastFullyDisabled = isIastFullyDisabled(appSecEnabled);
213215
remoteConfigEnabled =
214216
isFeatureEnabled(AgentFeature.REMOTE_CONFIG)
215217
|| isFeatureEnabled(AgentFeature.DEPRECATED_REMOTE_CONFIG);
@@ -739,7 +741,7 @@ private static boolean isSupportedAppSecArch() {
739741
}
740742

741743
private static void maybeStartIast(Class<?> scoClass, Object o) {
742-
if (iastEnabled) {
744+
if (iastEnabled || !iastFullyDisabled) {
743745

744746
StaticEventLogger.begin("IAST");
745747

@@ -1062,9 +1064,9 @@ private static boolean isFeatureEnabled(AgentFeature feature) {
10621064
}
10631065

10641066
/** @see datadog.trace.api.ProductActivation#fromString(String) */
1065-
private static boolean isAppSecFullyDisabled() {
1067+
private static boolean isFullyDisabled(final AgentFeature feature) {
10661068
// must be kept in sync with logic from Config!
1067-
final String featureEnabledSysprop = AgentFeature.APPSEC.systemProp;
1069+
final String featureEnabledSysprop = feature.systemProp;
10681070
String settingValue = getNullIfEmpty(System.getProperty(featureEnabledSysprop));
10691071
if (settingValue == null) {
10701072
settingValue = getNullIfEmpty(ddGetEnv(featureEnabledSysprop));
@@ -1078,6 +1080,14 @@ private static boolean isAppSecFullyDisabled() {
10781080
|| settingValue.equalsIgnoreCase("inactive"));
10791081
}
10801082

1083+
/** IAST will be enabled in opt-out if it's not actively disabled and AppSec is enabled */
1084+
private static boolean isIastFullyDisabled(final boolean isAppSecEnabled) {
1085+
if (isFullyDisabled(AgentFeature.IAST)) {
1086+
return true;
1087+
}
1088+
return !isAppSecEnabled;
1089+
}
1090+
10811091
private static String getNullIfEmpty(final String value) {
10821092
if (value == null || value.isEmpty()) {
10831093
return null;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.datadog.iast;
2+
3+
import com.datadog.iast.taint.TaintedObjects;
4+
import datadog.trace.api.iast.IastContext;
5+
import javax.annotation.Nonnull;
6+
import javax.annotation.Nullable;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
public class IastOptOutContext implements IastContext {
10+
11+
@SuppressWarnings("unchecked")
12+
@NotNull
13+
@Override
14+
public TaintedObjects getTaintedObjects() {
15+
return TaintedObjects.NoOp.INSTANCE;
16+
}
17+
18+
public static class Provider extends IastContext.Provider {
19+
20+
final IastContext optOutContext = new IastOptOutContext();
21+
22+
@Nullable
23+
@Override
24+
public IastContext resolve() {
25+
return optOutContext;
26+
}
27+
28+
@Override
29+
public IastContext buildRequestContext() {
30+
return new IastRequestContext(optOutContext.getTaintedObjects());
31+
}
32+
33+
@Override
34+
public void releaseRequestContext(@Nonnull final IastContext context) {
35+
// nothing to release in opt out mode
36+
}
37+
}
38+
}

‎dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java‎

Lines changed: 80 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.datadog.iast;
22

3+
import static datadog.trace.api.ProductActivation.FULLY_ENABLED;
34
import static datadog.trace.api.iast.IastContext.Mode.GLOBAL;
45
import static datadog.trace.api.iast.IastDetectionMode.UNLIMITED;
56

@@ -48,6 +49,8 @@
4849
import datadog.trace.api.iast.telemetry.Verbosity;
4950
import datadog.trace.util.AgentTaskScheduler;
5051
import datadog.trace.util.stacktrace.StackWalkerFactory;
52+
import java.lang.reflect.Constructor;
53+
import java.lang.reflect.UndeclaredThrowableException;
5154
import java.util.function.BiFunction;
5255
import java.util.function.Supplier;
5356
import java.util.stream.Stream;
@@ -67,16 +70,17 @@ public static void start(final SubscriptionService ss) {
6770
public static void start(
6871
final SubscriptionService ss, @Nullable OverheadController overheadController) {
6972
final Config config = Config.get();
70-
if (config.getIastActivation() != ProductActivation.FULLY_ENABLED) {
71-
LOGGER.debug("IAST is disabled");
73+
final ProductActivation iast = config.getIastActivation();
74+
final ProductActivation appSec = config.getAppSecActivation();
75+
if (iast != FULLY_ENABLED && appSec != FULLY_ENABLED) {
76+
LOGGER.debug("IAST is disabled: iast={}, appSec={}", iast, appSec);
7277
return;
7378
}
7479
DEBUG = config.isIastDebugEnabled();
7580
LOGGER.debug("IAST is starting: debug={}", DEBUG);
7681
final Reporter reporter = new Reporter(config, AgentTaskScheduler.INSTANCE);
7782
final boolean globalContext = config.getIastContextMode() == GLOBAL;
78-
final IastContext.Provider contextProvider =
79-
globalContext ? new IastGlobalContext.Provider() : new IastRequestContext.Provider();
83+
final IastContext.Provider contextProvider = contextProvider(iast, globalContext);
8084
if (overheadController == null) {
8185
overheadController =
8286
OverheadController.build(
@@ -90,43 +94,85 @@ public static void start(
9094
new Dependencies(
9195
config, reporter, overheadController, StackWalkerFactory.INSTANCE, contextProvider);
9296
final boolean addTelemetry = config.getIastTelemetryVerbosity() != Verbosity.OFF;
93-
iastModules(dependencies).forEach(InstrumentationBridge::registerIastModule);
97+
iastModules(iast, dependencies).forEach(InstrumentationBridge::registerIastModule);
9498
registerRequestStartedCallback(ss, addTelemetry, dependencies);
9599
registerRequestEndedCallback(ss, addTelemetry, dependencies);
96100
registerHeadersCallback(ss);
97101
registerGrpcServerRequestMessageCallback(ss);
98102
LOGGER.debug("IAST started");
99103
}
100104

101-
private static Stream<IastModule> iastModules(final Dependencies dependencies) {
102-
return Stream.of(
103-
new StringModuleImpl(),
104-
new FastCodecModule(),
105-
new SqlInjectionModuleImpl(dependencies),
106-
new PathTraversalModuleImpl(dependencies),
107-
new CommandInjectionModuleImpl(dependencies),
108-
new WeakCipherModuleImpl(dependencies),
109-
new WeakHashModuleImpl(dependencies),
110-
new LdapInjectionModuleImpl(dependencies),
111-
new PropagationModuleImpl(),
112-
new HttpResponseHeaderModuleImpl(dependencies),
113-
new HstsMissingHeaderModuleImpl(dependencies),
114-
new InsecureCookieModuleImpl(),
115-
new NoHttpOnlyCookieModuleImpl(),
116-
new XContentTypeModuleImpl(dependencies),
117-
new NoSameSiteCookieModuleImpl(),
118-
new SsrfModuleImpl(dependencies),
119-
new UnvalidatedRedirectModuleImpl(dependencies),
120-
new WeakRandomnessModuleImpl(dependencies),
121-
new XPathInjectionModuleImpl(dependencies),
122-
new TrustBoundaryViolationModuleImpl(dependencies),
123-
new XssModuleImpl(dependencies),
124-
new StacktraceLeakModuleImpl(dependencies),
125-
new HeaderInjectionModuleImpl(dependencies),
126-
new ApplicationModuleImpl(dependencies),
127-
new HardcodedSecretModuleImpl(dependencies),
128-
new InsecureAuthProtocolModuleImpl(dependencies),
129-
new ReflectionInjectionModuleImpl(dependencies));
105+
private static IastContext.Provider contextProvider(
106+
final ProductActivation iast, final boolean global) {
107+
if (iast != FULLY_ENABLED) {
108+
return new IastOptOutContext.Provider();
109+
} else {
110+
return global ? new IastGlobalContext.Provider() : new IastRequestContext.Provider();
111+
}
112+
}
113+
114+
private static Stream<IastModule> iastModules(
115+
final ProductActivation iast, final Dependencies dependencies) {
116+
Stream<Class<? extends IastModule>> modules =
117+
Stream.of(
118+
StringModuleImpl.class,
119+
FastCodecModule.class,
120+
SqlInjectionModuleImpl.class,
121+
PathTraversalModuleImpl.class,
122+
CommandInjectionModuleImpl.class,
123+
WeakCipherModuleImpl.class,
124+
WeakHashModuleImpl.class,
125+
LdapInjectionModuleImpl.class,
126+
PropagationModuleImpl.class,
127+
HttpResponseHeaderModuleImpl.class,
128+
HstsMissingHeaderModuleImpl.class,
129+
InsecureCookieModuleImpl.class,
130+
NoHttpOnlyCookieModuleImpl.class,
131+
XContentTypeModuleImpl.class,
132+
NoSameSiteCookieModuleImpl.class,
133+
SsrfModuleImpl.class,
134+
UnvalidatedRedirectModuleImpl.class,
135+
WeakRandomnessModuleImpl.class,
136+
XPathInjectionModuleImpl.class,
137+
TrustBoundaryViolationModuleImpl.class,
138+
XssModuleImpl.class,
139+
StacktraceLeakModuleImpl.class,
140+
HeaderInjectionModuleImpl.class,
141+
ApplicationModuleImpl.class,
142+
HardcodedSecretModuleImpl.class,
143+
InsecureAuthProtocolModuleImpl.class,
144+
ReflectionInjectionModuleImpl.class);
145+
if (iast != FULLY_ENABLED) {
146+
modules = modules.filter(IastSystem::isOptOut);
147+
}
148+
return modules.map(type -> newIastModule(dependencies, type));
149+
}
150+
151+
private static boolean isOptOut(final Class<? extends IastModule> module) {
152+
for (final Class<?> itf : module.getInterfaces()) {
153+
if (itf.getDeclaredAnnotation(IastModule.OptOut.class) != null) {
154+
return true;
155+
}
156+
}
157+
return false;
158+
}
159+
160+
@SuppressWarnings("unchecked")
161+
private static <M extends IastModule> M newIastModule(
162+
final Dependencies dependencies, final Class<M> type) {
163+
try {
164+
final Constructor<M> ctor = (Constructor<M>) type.getDeclaredConstructors()[0];
165+
if (ctor.getParameterCount() == 0) {
166+
return ctor.newInstance();
167+
} else {
168+
return ctor.newInstance(dependencies);
169+
}
170+
} catch (final Throwable e) {
171+
// should never happen and be caught on IAST tests
172+
throw new UndeclaredThrowableException(
173+
e,
174+
"Modules should have either default constructor or take only one param of type Dependencies");
175+
}
130176
}
131177

132178
private static void registerRequestStartedCallback(
Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,85 @@
11
package com.datadog.iast;
22

3+
import datadog.trace.api.Config;
4+
import datadog.trace.api.ProductActivation;
35
import datadog.trace.api.internal.TraceSegment;
46
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
57
import javax.annotation.Nullable;
68

79
public interface IastTag {
810

9-
Enabled SKIPPED = new Enabled(0);
10-
Enabled ANALYZED = new Enabled(1);
11+
void setTagTop(@Nullable final TraceSegment trace);
1112

12-
String key();
13+
void setTag(@Nullable final AgentSpan span);
1314

14-
Object value();
15+
abstract class BaseTag<E> implements IastTag {
1516

16-
default void setTagTop(@Nullable final TraceSegment trace) {
17-
if (trace != null) {
18-
trace.setTagTop(key(), value());
17+
protected abstract String key();
18+
19+
protected abstract E value();
20+
21+
@Override
22+
public void setTagTop(@Nullable final TraceSegment trace) {
23+
if (trace != null) {
24+
trace.setTagTop(key(), value());
25+
}
1926
}
20-
}
2127

22-
default void setTag(@Nullable final AgentSpan span) {
23-
if (span != null) {
24-
span.setTag(key(), value());
28+
@Override
29+
public void setTag(@Nullable final AgentSpan span) {
30+
if (span != null) {
31+
span.setTag(key(), value());
32+
}
2533
}
2634
}
2735

28-
class Enabled implements IastTag {
36+
class NoOp implements IastTag {
37+
private static final IastTag INSTANCE = new NoOp();
38+
39+
@Override
40+
public void setTagTop(@Nullable TraceSegment trace) {}
41+
42+
@Override
43+
public void setTag(@Nullable AgentSpan span) {}
44+
}
45+
46+
/**
47+
* Sets the value for {@code "_dd.iast.enabled"} in the requests, if IAST is not full activated it
48+
* should not set any values.
49+
*
50+
* <ul>
51+
* <li>{@code 0} for requests skipped by sampling.
52+
* <li>{@code 1} for requests analyzed by IAST.
53+
* </ul>
54+
*/
55+
class Enabled extends BaseTag<Integer> {
56+
57+
public static final IastTag SKIPPED = Enabled.withValue(0);
58+
59+
public static final IastTag ANALYZED = Enabled.withValue(1);
2960

3061
private final int value;
3162

32-
public Enabled(int value) {
63+
private Enabled(int value) {
3364
this.value = value;
3465
}
3566

3667
@Override
37-
public String key() {
68+
protected String key() {
3869
return "_dd.iast.enabled";
3970
}
4071

4172
@Override
42-
public Object value() {
73+
protected Integer value() {
4374
return value;
4475
}
76+
77+
public static IastTag withValue(final int value) {
78+
if (Config.get().getIastActivation() == ProductActivation.FULLY_ENABLED) {
79+
return new Enabled(value);
80+
} else {
81+
return NoOp.INSTANCE;
82+
}
83+
}
4584
}
4685
}

‎dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Reporter.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.datadog.iast;
22

3-
import static com.datadog.iast.IastTag.ANALYZED;
3+
import static com.datadog.iast.IastTag.Enabled.ANALYZED;
44
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
55

66
import com.datadog.iast.model.Vulnerability;

‎dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java‎

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

3-
import static com.datadog.iast.IastTag.ANALYZED;
4-
import static com.datadog.iast.IastTag.SKIPPED;
3+
import static com.datadog.iast.IastTag.Enabled.ANALYZED;
4+
import static com.datadog.iast.IastTag.Enabled.SKIPPED;
55

66
import com.datadog.iast.overhead.OverheadController;
77
import datadog.trace.api.gateway.Flow;

0 commit comments

Comments
 (0)