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
Original file line number Diff line number Diff line change
Expand Up @@ -1137,26 +1137,30 @@ private static void maybeStartAiGuard() {
private static void maybeStartAppSec(Class<?> scoClass, Object o) {

try {
// event tracking SDK must be available for customers even if AppSec is fully disabled
AppSecEventTracker.install();
} catch (final Exception e) {
log.debug("Error starting AppSec Event Tracker", e);
}
try {
// event tracking SDK must be available for customers even if AppSec is fully disabled
AppSecEventTracker.install();
} catch (final Exception e) {
log.debug("Error starting AppSec Event Tracker", e);
}

if (!(appSecEnabled || (remoteConfigEnabled && !appSecFullyDisabled))) {
return;
}
if (appSecEnabled || (remoteConfigEnabled && !appSecFullyDisabled)) {
StaticEventLogger.begin("AppSec");

StaticEventLogger.begin("AppSec");
try {
SubscriptionService ss =
AgentTracer.get().getSubscriptionService(RequestContextSlot.APPSEC);
startAppSec(ss, scoClass, o);
} catch (Exception e) {
log.error("Error starting AppSec System", e);
}

try {
SubscriptionService ss = AgentTracer.get().getSubscriptionService(RequestContextSlot.APPSEC);
startAppSec(ss, scoClass, o);
} catch (Exception e) {
log.error("Error starting AppSec System", e);
StaticEventLogger.end("AppSec");
}
} finally {
// RFC-1110: report the agentic-onboarding signal even if the AppSec startup attempt errored.
AgenticOnboarding.report();
}

StaticEventLogger.end("AppSec");
}

private static void startAppSec(SubscriptionService ss, Class<?> scoClass, Object sco) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package datadog.trace.bootstrap;

import static datadog.trace.bootstrap.config.provider.StableConfigSource.FLEET;
import static datadog.trace.bootstrap.config.provider.StableConfigSource.LOCAL;
import static datadog.trace.util.ConfigStrings.propertyNameToSystemPropertyName;
import static datadog.trace.util.ConfigStrings.toEnvVar;

import datadog.environment.EnvironmentVariables;
import datadog.environment.SystemProperties;
import datadog.trace.api.ConfigCollector;
import datadog.trace.api.ConfigOrigin;
import datadog.trace.api.ConfigSetting;
import datadog.trace.api.config.AppSecConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* RFC-1110: when the presence-only flag {@link AppSecConfig#APPSEC_AGENTIC_ONBOARDING} is set,
* report a boolean telemetry entry for whether AppSec fully came up (enabled and WAF started),
* tagged with the flag's origin. The flag's value is never read; unset means nothing is reported.
*/
final class AgenticOnboarding {
private static final Logger log = LoggerFactory.getLogger(AgenticOnboarding.class);

private AgenticOnboarding() {}

/** Report the signal for the current AppSec startup state; no-op when the flag is unset. */
static void report() {
try {
final ConfigOrigin origin = resolveOrigin();
report(ActiveSubsystems.APPSEC_WAF_STARTED, origin, configIdFor(origin));
} catch (Throwable t) {
log.debug("Unable to report agentic onboarding telemetry", t);
}
}

static void report(boolean wafStarted, ConfigOrigin origin, String configId) {
if (origin == null) {
return;
}
ConfigCollector.get()
.put(
AppSecConfig.APPSEC_AGENTIC_ONBOARDING,
wafStarted,
origin,
ConfigSetting.NON_DEFAULT_SEQ_ID,
configId);
}

private static ConfigOrigin resolveOrigin() {
final String key = AppSecConfig.APPSEC_AGENTIC_ONBOARDING;
final String sysProp = propertyNameToSystemPropertyName(key);
return originOf(
SystemProperties.get(sysProp),
FLEET.get(key),
EnvironmentVariables.get(toEnvVar(sysProp)),
LOCAL.get(key));
}

/**
* Stable-config id of the matched source, so fleet/local signals stay attributable; else null.
*/
private static String configIdFor(ConfigOrigin origin) {
if (origin == ConfigOrigin.FLEET_STABLE_CONFIG) {
return FLEET.getConfigId();
}
if (origin == ConfigOrigin.LOCAL_STABLE_CONFIG) {
return LOCAL.getConfigId();
}
return null;
}

/**
* Presence-only precedence jvm_prop &gt; fleet &gt; env &gt; local; any non-null value counts.
*/
static ConfigOrigin originOf(String jvmProp, String fleet, String env, String local) {
if (jvmProp != null) {
return ConfigOrigin.JVM_PROP;
}
if (fleet != null) {
return ConfigOrigin.FLEET_STABLE_CONFIG;
}
if (env != null) {
return ConfigOrigin.ENV;
}
if (local != null) {
return ConfigOrigin.LOCAL_STABLE_CONFIG;
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package datadog.trace.bootstrap;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import datadog.trace.api.ConfigCollector;
import datadog.trace.api.ConfigOrigin;
import datadog.trace.api.ConfigSetting;
import datadog.trace.api.config.AppSecConfig;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class AgenticOnboardingTest {

@BeforeEach
void drainCollector() {
// ConfigCollector is a JVM-wide singleton; drain it so each test sees only its own entries.
ConfigCollector.get().collect();
}

@Test
void originPresenceOnlyAndPrecedence() {
// unset in every source
assertNull(AgenticOnboarding.originOf(null, null, null, null));
// any non-null value counts as set, including an empty string
assertEquals(ConfigOrigin.JVM_PROP, AgenticOnboarding.originOf("", null, null, null));
assertEquals(
ConfigOrigin.FLEET_STABLE_CONFIG, AgenticOnboarding.originOf(null, "0", null, null));
assertEquals(ConfigOrigin.ENV, AgenticOnboarding.originOf(null, null, "false", null));
assertEquals(
ConfigOrigin.LOCAL_STABLE_CONFIG, AgenticOnboarding.originOf(null, null, null, "x"));
// precedence jvm_prop > fleet > env > local
assertEquals(ConfigOrigin.JVM_PROP, AgenticOnboarding.originOf("a", "b", "c", "d"));
assertEquals(ConfigOrigin.FLEET_STABLE_CONFIG, AgenticOnboarding.originOf(null, "b", "c", "d"));
assertEquals(ConfigOrigin.ENV, AgenticOnboarding.originOf(null, null, "c", "d"));
}

@Test
void reportsNothingWhenFlagUnset() {
AgenticOnboarding.report(true, null, null);
assertNull(find());
}

@Test
void reportsBooleanWithOriginAndNoConfigIdForEnv() {
AgenticOnboarding.report(true, ConfigOrigin.ENV, null);
ConfigSetting setting = find();
assertEquals(Boolean.TRUE, setting.value);
assertEquals(ConfigOrigin.ENV, setting.origin);
assertNull(setting.configId);
}

@Test
void reportsFalseWithStableConfigId() {
AgenticOnboarding.report(false, ConfigOrigin.FLEET_STABLE_CONFIG, "cfg-42");
ConfigSetting setting = find();
assertEquals(Boolean.FALSE, setting.value);
assertEquals(ConfigOrigin.FLEET_STABLE_CONFIG, setting.origin);
assertEquals("cfg-42", setting.configId);
}

@Test
void reportReadsSystemPropertyAndActiveState() {
final String sysProp = "dd.appsec.agentic_onboarding";
final String previousProp = System.getProperty(sysProp);
final boolean previous = ActiveSubsystems.APPSEC_WAF_STARTED;
System.setProperty(sysProp, "anything");
ActiveSubsystems.APPSEC_WAF_STARTED = true;
try {
AgenticOnboarding.report();
ConfigSetting setting = find();
assertEquals(Boolean.TRUE, setting.value);
assertEquals(ConfigOrigin.JVM_PROP, setting.origin);
} finally {
if (previousProp == null) {
System.clearProperty(sysProp);
} else {
System.setProperty(sysProp, previousProp);
}
ActiveSubsystems.APPSEC_WAF_STARTED = previous;
}
}

/** Drains the collector and returns the reported agentic-onboarding setting, or {@code null}. */
private static ConfigSetting find() {
for (Map<String, ConfigSetting> byKey : ConfigCollector.get().collect().values()) {
ConfigSetting setting = byKey.get(AppSecConfig.APPSEC_AGENTIC_ONBOARDING);
if (setting != null) {
return setting;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public static void start(SubscriptionService gw, SharedCommunicationObjects sco)
private static void doStart(SubscriptionService gw, SharedCommunicationObjects sco) {
final Config config = Config.get();
ProductActivation appSecEnabledConfig = config.getAppSecActivation();
// RFC-1110: cleared per start attempt; set below only once AppSec is fully up with its WAF.
ActiveSubsystems.APPSEC_WAF_STARTED = false;
if (appSecEnabledConfig == ProductActivation.FULLY_DISABLED) {
log.debug("AppSec: disabled");
return;
Expand Down Expand Up @@ -103,6 +105,11 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s

STARTED.set(true);

// RFC-1110: AppSec fully up only when active and its WAF module started (a swallowed WAF init
// failure leaves AppSec active but STARTED_MODULES_INFO empty).
ActiveSubsystems.APPSEC_WAF_STARTED =
ActiveSubsystems.APPSEC_ACTIVE && !STARTED_MODULES_INFO.isEmpty();

String startedAppSecModules = String.join(", ", STARTED_MODULES_INFO.values());
if (appSecEnabledConfig == ProductActivation.FULLY_ENABLED) {
log.info("AppSec is {} with {}", appSecEnabledConfig, startedAppSecModules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
public final class AppSecConfig {

public static final String APPSEC_ENABLED = "appsec.enabled";

/** RFC-1110 presence-only flag; its value is never read, only reported as telemetry. */
public static final String APPSEC_AGENTIC_ONBOARDING = "appsec.agentic_onboarding";

public static final String APPSEC_RULES_FILE = "appsec.rules";
public static final String APPSEC_IP_ADDR_HEADER = "appsec.ipheader";
public static final String APPSEC_TRACE_RATE_LIMIT = "appsec.trace.rate.limit";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@

public class ActiveSubsystems {
public static volatile boolean APPSEC_ACTIVE;

/**
* Whether AppSec came up with its WAF started; stricter than {@link #APPSEC_ACTIVE} (RFC-1110).
*/
public static volatile boolean APPSEC_WAF_STARTED;
}
8 changes: 8 additions & 0 deletions metadata/supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@
"aliases": []
}
],
"DD_APPSEC_AGENTIC_ONBOARDING": [
{
"version": "A",
"type": "string",
"default": null,
"aliases": []
}
],
"DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING": [
{
"version": "C",
Expand Down