Skip to content

Commit

Permalink
Appsec: create top span for process executions
Browse files Browse the repository at this point in the history
  • Loading branch information
cataphract committed Oct 25, 2022
1 parent f90580f commit d3410f2
Show file tree
Hide file tree
Showing 17 changed files with 503 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
0 java.security.MessageDigest
# allow exception profiling instrumentation
0 java.lang.Throwable
# allow ProcessImpl instrumentation
0 java.lang.ProcessImpl
0 java.net.HttpURLConnection
0 java.net.URL
0 java.nio.DirectByteBuffer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import datadog.trace.api.ProductActivationConfig;
import datadog.trace.api.gateway.SubscriptionService;
import datadog.trace.api.time.SystemTimeSource;
import datadog.trace.bootstrap.ActiveSubsystems;
import datadog.trace.util.Strings;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -29,8 +30,6 @@

public class AppSecSystem {

public static volatile boolean ACTIVE;

private static final Logger log = LoggerFactory.getLogger(AppSecSystem.class);
private static final AtomicBoolean STARTED = new AtomicBoolean();
private static final Map<AppSecModule, String> STARTED_MODULES_INFO = new HashMap<>();
Expand All @@ -44,6 +43,7 @@ public static void start(SubscriptionService gw, SharedCommunicationObjects sco)
throw ase;
} catch (RuntimeException | Error e) {
StandardizedLogging.appSecStartupError(log, e);
setActive(false);
throw new AbortStartupException(e);
}
}
Expand All @@ -57,7 +57,6 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s
}
log.info("AppSec is starting ({})", appSecEnabledConfig);

ACTIVE = appSecEnabledConfig == ProductActivationConfig.FULLY_ENABLED;
REPLACEABLE_EVENT_PRODUCER = new ReplaceableEventProducerService();
EventDispatcher eventDispatcher = new EventDispatcher();
REPLACEABLE_EVENT_PRODUCER.replaceEventProducerService(eventDispatcher);
Expand All @@ -82,6 +81,8 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s
loadModules(eventDispatcher);
gatewayBridge.init();

setActive(appSecEnabledConfig == ProductActivationConfig.FULLY_ENABLED);

APP_SEC_CONFIG_SERVICE.maybeSubscribeConfigPolling();

STARTED.set(true);
Expand All @@ -102,6 +103,14 @@ private static RateLimiter getRateLimiter(Config config, Monitoring monitoring)
return rateLimiter;
}

public static boolean isActive() {
return ActiveSubsystems.APPSEC_ACTIVE;
}

public static void setActive(boolean status) {
ActiveSubsystems.APPSEC_ACTIVE = status;
}

public static void stop() {
if (!STARTED.getAndSet(false)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private void subscribeConfigurationPoller() {
distributeSubConfigurations(configMap, reconfiguration);
log.info(
"New AppSec configuration has been applied. AppSec status: {}",
AppSecSystem.ACTIVE ? "active" : "inactive");
AppSecSystem.isActive() ? "active" : "inactive");
});
this.configurationPoller.addListener(
Product.ASM_DATA,
Expand Down Expand Up @@ -107,9 +107,9 @@ private void subscribeConfigurationPoller() {
(configKey, newConfig, hinter) -> {
final boolean newState =
newConfig != null && newConfig.asm != null && newConfig.asm.enabled;
if (AppSecSystem.ACTIVE != newState) {
if (AppSecSystem.isActive() != newState) {
log.warn("AppSec {} (runtime)", newState ? "enabled" : "disabled");
AppSecSystem.ACTIVE = newState;
AppSecSystem.setActive(newState);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void init() {
subscriptionService.registerCallback(
events.requestStarted(),
() -> {
if (!AppSecSystem.ACTIVE) {
if (!AppSecSystem.isActive()) {
return RequestContextSupplier.EMPTY;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
def initialRulesOverride

when:
AppSecSystem.ACTIVE = false
AppSecSystem.active = false
appSecConfigService.init()
appSecConfigService.maybeSubscribeConfigPolling()
def configurer = appSecConfigService.createAppSecModuleConfigurer()
Expand Down Expand Up @@ -160,50 +160,50 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
1 * wafDataListener.onNewSubconfig([[id: 'foo', type: '', data: []]], _)
1 * wafRulesOverrideListener.onNewSubconfig([foo: false], _)
0 * _._
AppSecSystem.ACTIVE == true
AppSecSystem.active == true

when:
savedFeaturesListener.accept('config_key',
savedFeaturesDeserializer.deserialize('{"asm":{"enabled": false}}'.bytes),
ConfigurationChangesListener.PollingRateHinter.NOOP)

then:
AppSecSystem.ACTIVE == false
AppSecSystem.active == false

when: 'switch back to enabled'
savedFeaturesListener.accept('config_key',
savedFeaturesDeserializer.deserialize('{"asm":{"enabled": true}}'.bytes),
ConfigurationChangesListener.PollingRateHinter.NOOP)

then: 'it is enabled again'
AppSecSystem.ACTIVE == true
AppSecSystem.active == true

when: 'asm are not set'
savedFeaturesListener.accept('config_key',
savedFeaturesDeserializer.deserialize('{}'.bytes),
ConfigurationChangesListener.PollingRateHinter.NOOP)

then: 'it is disabled (<not set> == false)'
AppSecSystem.ACTIVE == false
AppSecSystem.active == false

when: 'switch back to enabled'
savedFeaturesListener.accept('config_key',
savedFeaturesDeserializer.deserialize('{"asm":{"enabled": true}}'.bytes),
ConfigurationChangesListener.PollingRateHinter.NOOP)

then: 'it is enabled again'
AppSecSystem.ACTIVE == true
AppSecSystem.active == true

when: 'asm features are not set'
savedFeaturesListener.accept('config_key',
null,
ConfigurationChangesListener.PollingRateHinter.NOOP)

then: 'it is disabled (<not set> == false)'
AppSecSystem.ACTIVE == false
AppSecSystem.active == false

cleanup:
AppSecSystem.ACTIVE = true
AppSecSystem.active = true
}

void 'stopping appsec unsubscribes from the poller'() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class GatewayBridgeSpecification extends DDSpecification {

void 'request_start returns null context if appsec is disabled'() {
setup:
AppSecSystem.ACTIVE = false
AppSecSystem.active = false

when:
Flow<AppSecRequestContext> startFlow = requestStartedCB.get()
Expand All @@ -105,7 +105,7 @@ class GatewayBridgeSpecification extends DDSpecification {
0 * _._

cleanup:
AppSecSystem.ACTIVE = true
AppSecSystem.active = true
}

void 'request_end closes context reports attacks and publishes event'() {
Expand Down
11 changes: 11 additions & 0 deletions dd-java-agent/instrumentation/java-lang/java-lang.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
}

muzzle {
pass {
coreJdk()
}
}

apply from: "$rootDir/gradle/java.gradle"
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package datadog.trace.instrumentation.java.lang;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.Platform;
import java.util.Map;

@AutoService(Instrumenter.class)
public class ProcessImplInstrumentation extends Instrumenter.AppSec
implements Instrumenter.ForSingleType, Instrumenter.ForBootstrap {

public ProcessImplInstrumentation() {
super("java-lang-appsec");
}

@Override
public String instrumentedType() {
return "java.lang.ProcessImpl";
}

@Override
public boolean isEnabled() {
return Platform.isJavaVersionAtLeast(8) && super.isEnabled();
}

@Override
public void adviceTransformations(AdviceTransformation transformation) {
transformation.applyAdvice(
named("start")
.and(
takesArguments(
String[].class,
Map.class,
String.class,
ProcessBuilder.Redirect[].class,
boolean.class)),
packageName + ".ProcessImplStartAdvice");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package datadog.trace.instrumentation.java.lang;

import datadog.trace.bootstrap.ActiveSubsystems;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.api.TagContext;
import datadog.trace.bootstrap.instrumentation.api8.java.lang.ProcessImplInstrumentationHelpers;
import java.io.IOException;
import java.util.Map;
import net.bytebuddy.asm.Advice;

class ProcessImplStartAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentSpan startSpan(@Advice.Argument(0) final String[] command) throws IOException {
if (!ProcessImplInstrumentationHelpers.ONLINE) {
return null;
}

AgentTracer.TracerAPI tracer = AgentTracer.get();

if (!ActiveSubsystems.APPSEC_ACTIVE) {
return null;
}

Map<String, String> tags = ProcessImplInstrumentationHelpers.createTags(command);
TagContext tagContext = new TagContext("appsec", tags);
AgentSpan span = tracer.startSpan("command_execution", tagContext, true);
span.setSpanType("system");
span.setResourceName(ProcessImplInstrumentationHelpers.determineResource(command));
span.startThreadMigration();
return span;
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void endSpan(
@Advice.Return Process p, @Advice.Enter AgentSpan span, @Advice.Thrown Throwable t) {
if (span == null) {
return;
}
if (t != null) {
span.finishThreadMigration();
span.setError(true);
span.setErrorMessage(t.getMessage());
span.finish();
return;
}

ProcessImplInstrumentationHelpers.addProcessCompletionHook(p, span);
}
}
Loading

0 comments on commit d3410f2

Please sign in to comment.