2121import static datadog .remoteconfig .Capabilities .CAPABILITY_ASM_TRUSTED_IPS ;
2222import static datadog .remoteconfig .Capabilities .CAPABILITY_ASM_USER_BLOCKING ;
2323import static datadog .remoteconfig .Capabilities .CAPABILITY_ENDPOINT_FINGERPRINT ;
24+ import static datadog .trace .logging .LogLevel .ERROR ;
2425
2526import com .datadog .appsec .AppSecModule ;
2627import com .datadog .appsec .AppSecSystem ;
4748import datadog .trace .api .Config ;
4849import datadog .trace .api .ProductActivation ;
4950import datadog .trace .api .UserIdCollectionMode ;
51+ import datadog .trace .api .telemetry .LogCollector ;
5052import java .io .ByteArrayInputStream ;
5153import java .io .FileInputStream ;
5254import java .io .FileNotFoundException ;
6062import java .util .Map ;
6163import java .util .Set ;
6264import java .util .concurrent .ConcurrentHashMap ;
63-
64- import datadog .trace .api .telemetry .LogCollector ;
6565import okio .Okio ;
6666import org .slf4j .Logger ;
6767import org .slf4j .LoggerFactory ;
@@ -101,6 +101,10 @@ public class AppSecConfigServiceImpl implements AppSecConfigService {
101101 private final String DEFAULT_WAF_CONFIG_RULE = "DEFAULT_WAF_CONFIG" ;
102102 private String currentRuleVersion ;
103103 private List <AppSecModule > modulesToUpdateVersionIn ;
104+ private final LogCollector telemetryLogger = LogCollector .get ();
105+
106+ Moshi moshi = new Moshi .Builder ().build ();
107+ JsonAdapter <Map > mapToJsonAdapter ;
104108
105109 public AppSecConfigServiceImpl (
106110 Config tracerConfig ,
@@ -113,6 +117,7 @@ public AppSecConfigServiceImpl(
113117 if (tracerConfig .isAppSecWafMetrics ()) {
114118 traceSegmentPostProcessors .add (statsReporter );
115119 }
120+ mapToJsonAdapter = moshi .adapter (Map .class );
116121 }
117122
118123 private void subscribeConfigurationPoller () {
@@ -156,11 +161,11 @@ private void subscribeConfigurationPoller() {
156161 }
157162
158163 private void subscribeRulesAndData () {
159- this .configurationPoller .addListener (Product .ASM_DD , new AsmDDTypedListener ());
164+ this .configurationPoller .addListener (Product .ASM_DD , new AsmDDTypedListener (Product . ASM_DD ));
160165 this .configurationPoller .addListener (
161- Product .ASM_DATA , new AppSecConfigConfigurationChangesTypedListener ());
166+ Product .ASM_DATA , new AppSecConfigConfigurationChangesTypedListener (Product . ASM_DATA ));
162167 this .configurationPoller .addListener (
163- Product .ASM , new AppSecConfigConfigurationChangesTypedListener ());
168+ Product .ASM , new AppSecConfigConfigurationChangesTypedListener (Product . ASM ));
164169 }
165170
166171 public void modulesToUpdateVersionIn (List <AppSecModule > modules ) {
@@ -172,6 +177,12 @@ public String getCurrentRuleVersion() {
172177 }
173178
174179 private class AppSecConfigConfigurationChangesTypedListener implements ProductListener {
180+ private Product productType ;
181+
182+ public AppSecConfigConfigurationChangesTypedListener (Product product ) {
183+ this .productType = product ;
184+ }
185+
175186 @ Override
176187 public void accept (ConfigKey configKey , byte [] content , PollingRateHinter pollingRateHinter )
177188 throws IOException {
@@ -189,7 +200,7 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollin
189200 Map <String , Object > contentMap =
190201 ADAPTER .fromJson (Okio .buffer (Okio .source (new ByteArrayInputStream (content ))));
191202 try {
192- handleWafUpdateResultReport (configKey .toString (), contentMap );
203+ handleWafUpdateResultReport (configKey .toString (), contentMap , productType );
193204 } catch (AppSecModule .AppSecModuleActivationException e ) {
194205 throw new RuntimeException (e );
195206 }
@@ -209,6 +220,10 @@ public void commit(PollingRateHinter pollingRateHinter) {
209220 }
210221
211222 private class AsmDDTypedListener extends AppSecConfigConfigurationChangesTypedListener {
223+ public AsmDDTypedListener (Product product ) {
224+ super (product );
225+ }
226+
212227 @ Override
213228 public void accept (ConfigKey configKey , byte [] content , PollingRateHinter pollingRateHinter )
214229 throws IOException {
@@ -222,7 +237,11 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollin
222237 defaultConfigActivated = false ;
223238 }
224239 super .accept (configKey , content , pollingRateHinter );
225- usedDDWafConfigKeys .add (configKey .toString ());
240+ if (content == null ) {
241+ usedDDWafConfigKeys .remove (configKey .toString ());
242+ } else {
243+ usedDDWafConfigKeys .add (configKey .toString ());
244+ }
226245 }
227246
228247 @ Override
@@ -233,7 +252,8 @@ public void remove(ConfigKey configKey, PollingRateHinter pollingRateHinter)
233252 }
234253 }
235254
236- private void handleWafUpdateResultReport (String configKey , Map <String , Object > rawConfig )
255+ private void handleWafUpdateResultReport (
256+ String configKey , Map <String , Object > rawConfig , Product productType )
237257 throws AppSecModule .AppSecModuleActivationException {
238258 wafBuilder = getWafBuilder ();
239259 if (modulesToUpdateVersionIn != null
@@ -247,9 +267,6 @@ private void handleWafUpdateResultReport(String configKey, Map<String, Object> r
247267 StandardizedLogging .numLoadedRules (log , configKey , countRules (rawConfig ));
248268 }
249269
250- // TODO: Send diagnostics via telemetry
251- final LogCollector telemetryLogger = LogCollector .get ();
252-
253270 initReporter .setReportForPublication (wafDiagnostics );
254271 if (wafDiagnostics .rulesetVersion != null
255272 && !wafDiagnostics .rulesetVersion .isEmpty ()
@@ -261,13 +278,17 @@ private void handleWafUpdateResultReport(String configKey, Map<String, Object> r
261278 modulesToUpdateVersionIn .forEach (module -> module .setRuleVersion (currentRuleVersion ));
262279 }
263280 }
281+ if (wafDiagnostics .getNumConfigError () > 0 ) {
282+ addTelemetryErrorLog (wafDiagnostics );
283+ }
264284 } catch (InvalidRuleSetException e ) {
265285 log .debug (
266286 "Invalid rule during waf config update for config key {}: {}" ,
267287 configKey ,
268288 e .wafDiagnostics );
269289
270- // TODO: Propagate diagostics back to remote config apply_error
290+ addTelemetryErrorLog (e .wafDiagnostics );
291+ sendErrorToRemoteConfig (e .wafDiagnostics , productType );
271292
272293 initReporter .setReportForPublication (e .wafDiagnostics );
273294 throw new RuntimeException (e );
@@ -277,6 +298,117 @@ private void handleWafUpdateResultReport(String configKey, Map<String, Object> r
277298 }
278299 }
279300
301+ private void sendErrorToRemoteConfig (WafDiagnostics wafDiagnostics , Product productType ) {
302+ if (wafDiagnostics .rules != null ) {
303+ getRemoteConfigErrorLogFor ("rules" , wafDiagnostics .rules .getErrors (), productType );
304+ }
305+ if (wafDiagnostics .customRules != null ) {
306+ getRemoteConfigErrorLogFor (
307+ "customRules" , wafDiagnostics .customRules .getErrors (), productType );
308+ }
309+ if (wafDiagnostics .rulesData != null ) {
310+ getRemoteConfigErrorLogFor ("rulesData" , wafDiagnostics .rulesData .getErrors (), productType );
311+ }
312+ if (wafDiagnostics .rulesOverride != null ) {
313+ getRemoteConfigErrorLogFor (
314+ "rulesOverride" , wafDiagnostics .rulesOverride .getErrors (), productType );
315+ }
316+ if (wafDiagnostics .exclusions != null ) {
317+ getRemoteConfigErrorLogFor ("exclusions" , wafDiagnostics .exclusions .getErrors (), productType );
318+ }
319+ if (wafDiagnostics .exclusionData != null ) {
320+ getRemoteConfigErrorLogFor (
321+ "exclusionData" , wafDiagnostics .exclusionData .getErrors (), productType );
322+ }
323+ if (wafDiagnostics .actions != null ) {
324+ getRemoteConfigErrorLogFor ("actions" , wafDiagnostics .actions .getErrors (), productType );
325+ }
326+ if (wafDiagnostics .processors != null ) {
327+ getRemoteConfigErrorLogFor ("processors" , wafDiagnostics .processors .getErrors (), productType );
328+ }
329+ if (wafDiagnostics .scanners != null ) {
330+ getRemoteConfigErrorLogFor ("scanners" , wafDiagnostics .scanners .getErrors (), productType );
331+ }
332+ }
333+
334+ private void getRemoteConfigErrorLogFor (
335+ String configType , Map <String , List <String >> errors , Product productType ) {
336+ if (productType == null ) {
337+ return ; // no need for RC error upon initialization
338+ }
339+
340+ String error = "{" ;
341+ Set <String > messageKeySet = errors .keySet ();
342+ for (String key : messageKeySet ) {
343+ error = error + "\" message\" : \" " + key + "\" : [" ;
344+ List <String > errorsPerKey = errors .get (key );
345+ for (int i = 0 ; i < errorsPerKey .size (); i ++) {
346+ error = error + "\" " + errorsPerKey .get (i ) + "\" " ;
347+ if (i < errorsPerKey .size () - 1 ) {
348+ error = error + "," ;
349+ }
350+ }
351+ error = error + "]," ;
352+
353+ error = error + "\" level\" : \" ERROR\" ," ;
354+
355+ error =
356+ error
357+ + "\" tags\" : {"
358+ + "\" log_type\" : \" rc::"
359+ + productType .name ().toLowerCase ()
360+ + "::diagnostic\" "
361+ + "\" appsec_config_key\" : \" "
362+ + configType
363+ + "\" "
364+ + "\" rc_config_id\" : \" \" }" ;
365+ error = error + "}," ;
366+ }
367+
368+ error = error .substring (0 , error .length () - 1 ); // remove last comma
369+
370+ throw new RuntimeException (error );
371+ }
372+
373+ private void addTelemetryErrorLog (WafDiagnostics wafDiagnostics ) {
374+ if (wafDiagnostics .rules != null ) {
375+ addTelemetryErrorLogFor ("rules" , wafDiagnostics .rules .getErrors ());
376+ }
377+ if (wafDiagnostics .customRules != null ) {
378+ addTelemetryErrorLogFor ("customRules" , wafDiagnostics .customRules .getErrors ());
379+ }
380+ if (wafDiagnostics .rulesData != null ) {
381+ addTelemetryErrorLogFor ("rulesData" , wafDiagnostics .rulesData .getErrors ());
382+ }
383+ if (wafDiagnostics .rulesOverride != null ) {
384+ addTelemetryErrorLogFor ("rulesOverride" , wafDiagnostics .rulesOverride .getErrors ());
385+ }
386+ if (wafDiagnostics .exclusions != null ) {
387+ addTelemetryErrorLogFor ("exclusions" , wafDiagnostics .exclusions .getErrors ());
388+ }
389+ if (wafDiagnostics .exclusionData != null ) {
390+ addTelemetryErrorLogFor ("exclusionData" , wafDiagnostics .exclusionData .getErrors ());
391+ }
392+ if (wafDiagnostics .actions != null ) {
393+ addTelemetryErrorLogFor ("actions" , wafDiagnostics .actions .getErrors ());
394+ }
395+ if (wafDiagnostics .processors != null ) {
396+ addTelemetryErrorLogFor ("processors" , wafDiagnostics .processors .getErrors ());
397+ }
398+ if (wafDiagnostics .scanners != null ) {
399+ addTelemetryErrorLogFor ("scanners" , wafDiagnostics .scanners .getErrors ());
400+ }
401+ }
402+
403+ private void addTelemetryErrorLogFor (String section , Map <String , List <String >> errors ) {
404+ if (!errors .isEmpty ()) {
405+ String error =
406+ "{\" " + section + "\" : {\" errors\" :" + mapToJsonAdapter .toJson (errors ) + "}}" ;
407+ telemetryLogger .addLogMessage (
408+ ERROR .name (), error , new AppSecModule .AppSecModuleActivationException (error ));
409+ }
410+ }
411+
280412 private void subscribeAsmFeatures () {
281413 this .configurationPoller .addListener (
282414 Product .ASM_FEATURES ,
@@ -348,10 +480,7 @@ public void init() {
348480 throw new IllegalStateException ("Expected default waf config to be available" );
349481 }
350482 try {
351- handleWafUpdateResultReport (
352- DEFAULT_WAF_CONFIG_RULE ,
353- wafConfig ,
354- defaultConfigActivated ? DEFAULT_CONFIG_LOCATION : tracerConfig .getAppSecRulesFile ());
483+ handleWafUpdateResultReport (DEFAULT_WAF_CONFIG_RULE , wafConfig , null );
355484 } catch (AppSecModule .AppSecModuleActivationException e ) {
356485 throw new RuntimeException (e );
357486 }
0 commit comments