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 .api .ConfigOrigin .DEFAULT ;
25+ import static datadog .trace .api .ConfigOrigin .LOCAL_STABLE_CONFIG ;
2426
2527import com .datadog .appsec .AppSecModule ;
2628import com .datadog .appsec .AppSecSystem ;
4547import datadog .remoteconfig .state .ConfigKey ;
4648import datadog .remoteconfig .state .ProductListener ;
4749import datadog .trace .api .Config ;
50+ import datadog .trace .api .ConfigOrigin ;
4851import datadog .trace .api .ProductActivation ;
4952import datadog .trace .api .UserIdCollectionMode ;
5053import datadog .trace .api .telemetry .LogCollector ;
5154import datadog .trace .api .telemetry .WafMetricCollector ;
5255import java .io .ByteArrayInputStream ;
56+ import java .io .File ;
5357import java .io .FileInputStream ;
5458import java .io .FileNotFoundException ;
5559import java .io .IOException ;
5660import java .io .InputStream ;
61+ import java .nio .file .Paths ;
5762import java .util .ArrayList ;
5863import java .util .Collections ;
5964import java .util .HashMap ;
60- import java .util .HashSet ;
6165import java .util .List ;
6266import java .util .Map ;
6367import java .util .Set ;
@@ -71,12 +75,12 @@ public class AppSecConfigServiceImpl implements AppSecConfigService {
7175 private static final Logger log = LoggerFactory .getLogger (AppSecConfigServiceImpl .class );
7276
7377 private static final String DEFAULT_CONFIG_LOCATION = "default_config.json" ;
78+ private static final String DEFAULT_WAF_CONFIG_RULE = "DEFAULT_WAF_CONFIG" ;
7479
7580 private final ConfigurationPoller configurationPoller ;
7681 private WafBuilder wafBuilder ;
7782
78- private MergedAsmFeatures mergedAsmFeatures ;
79- private volatile boolean initialized ;
83+ private final MergedAsmFeatures mergedAsmFeatures = new MergedAsmFeatures ();
8084
8185 private final ConcurrentHashMap <String , SubconfigListener > subconfigListeners =
8286 new ConcurrentHashMap <>();
@@ -95,10 +99,9 @@ public class AppSecConfigServiceImpl implements AppSecConfigService {
9599 .build ()
96100 .adapter (Types .newParameterizedType (Map .class , String .class , Object .class ));
97101
98- private boolean hasUserWafConfig ;
99- private boolean defaultConfigActivated ;
100- private final Set <String > usedDDWafConfigKeys = new HashSet <>();
101- private final String DEFAULT_WAF_CONFIG_RULE = "DEFAULT_WAF_CONFIG" ;
102+ private ConfigOrigin wafConfigOrigin ;
103+ private final Set <String > usedDDWafConfigKeys =
104+ Collections .newSetFromMap (new ConcurrentHashMap <>());
102105 private String currentRuleVersion ;
103106 private List <AppSecModule > modulesToUpdateVersionIn ;
104107
@@ -113,13 +116,14 @@ public AppSecConfigServiceImpl(
113116 if (tracerConfig .isAppSecWafMetrics ()) {
114117 traceSegmentPostProcessors .add (statsReporter );
115118 }
119+ wafConfigOrigin = hasUserWafConfig (tracerConfig ) ? LOCAL_STABLE_CONFIG : DEFAULT ;
116120 }
117121
118122 private void subscribeConfigurationPoller () {
119123 // see also close() method
120124 subscribeAsmFeatures ();
121125
122- if (! hasUserWafConfig ) {
126+ if (wafConfigOrigin != LOCAL_STABLE_CONFIG ) {
123127 subscribeRulesAndData ();
124128 } else {
125129 log .debug ("Will not subscribe to ASM, ASM_DD and ASM_DATA (AppSec custom rules in use)" );
@@ -173,16 +177,10 @@ private class AppSecConfigChangesListener implements ProductListener {
173177 @ Override
174178 public void accept (ConfigKey configKey , byte [] content , PollingRateHinter pollingRateHinter )
175179 throws IOException {
176- if (!initialized ) {
177- throw new IllegalStateException ();
178- }
180+ maybeApplyDefaultConfig ();
179181
180182 if (content == null ) {
181- try {
182- wafBuilder .removeConfig (configKey .toString ());
183- } catch (UnclassifiedWafException e ) {
184- throw new RuntimeException (e );
185- }
183+ remove (configKey , pollingRateHinter );
186184 } else {
187185 Map <String , Object > contentMap =
188186 ADAPTER .fromJson (Okio .buffer (Okio .source (new ByteArrayInputStream (content ))));
@@ -197,7 +195,11 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollin
197195 @ Override
198196 public void remove (ConfigKey configKey , PollingRateHinter pollingRateHinter )
199197 throws IOException {
200- accept (configKey , null , pollingRateHinter );
198+ try {
199+ wafBuilder .removeConfig (configKey .toString ());
200+ } catch (UnclassifiedWafException e ) {
201+ throw new RuntimeException (e );
202+ }
201203 }
202204
203205 @ Override
@@ -206,28 +208,48 @@ public void commit(PollingRateHinter pollingRateHinter) {
206208 }
207209 }
208210
209- private class AppSecConfigChangesDDListener extends AppSecConfigChangesListener {
211+ private class AppSecConfigChangesDDListener implements ProductListener {
210212 @ Override
211213 public void accept (ConfigKey configKey , byte [] content , PollingRateHinter pollingRateHinter )
212214 throws IOException {
213- if (defaultConfigActivated ) { // if we get any config, remove the default one
214- log .debug ("Removing default config" );
215+ if (content == null ) {
216+ remove (configKey , pollingRateHinter );
217+ } else {
218+ if (usedDDWafConfigKeys .remove (DEFAULT_WAF_CONFIG_RULE )) {
219+ log .debug ("Removing default config" );
220+ try {
221+ wafBuilder .removeConfig (DEFAULT_WAF_CONFIG_RULE );
222+ } catch (UnclassifiedWafException e ) {
223+ throw new RuntimeException ("Error removing default WAF config" , e );
224+ }
225+ }
226+ Map <String , Object > contentMap =
227+ ADAPTER .fromJson (Okio .buffer (Okio .source (new ByteArrayInputStream (content ))));
215228 try {
216- wafBuilder .removeConfig (DEFAULT_WAF_CONFIG_RULE );
217- } catch (UnclassifiedWafException e ) {
229+ final String key = configKey .toString ();
230+ handleWafUpdateResultReport (key , contentMap );
231+ usedDDWafConfigKeys .add (key );
232+ } catch (AppSecModule .AppSecModuleActivationException e ) {
218233 throw new RuntimeException (e );
219234 }
220- defaultConfigActivated = false ;
221235 }
222- super .accept (configKey , content , pollingRateHinter );
223- usedDDWafConfigKeys .add (configKey .toString ());
224236 }
225237
226238 @ Override
227239 public void remove (ConfigKey configKey , PollingRateHinter pollingRateHinter )
228240 throws IOException {
229- super .remove (configKey , pollingRateHinter );
230- usedDDWafConfigKeys .remove (configKey .toString ());
241+ try {
242+ final String key = configKey .toString ();
243+ wafBuilder .removeConfig (key );
244+ usedDDWafConfigKeys .remove (key );
245+ } catch (UnclassifiedWafException e ) {
246+ throw new RuntimeException (e );
247+ }
248+ }
249+
250+ @ Override
251+ public void commit (PollingRateHinter pollingRateHinter ) {
252+ // no action needed
231253 }
232254 }
233255
@@ -252,7 +274,8 @@ private void handleWafUpdateResultReport(String configKey, Map<String, Object> r
252274 if (wafDiagnostics .rulesetVersion != null
253275 && !wafDiagnostics .rulesetVersion .isEmpty ()
254276 && !wafDiagnostics .rules .getLoaded ().isEmpty ()
255- && (!defaultConfigActivated || currentRuleVersion == null )) {
277+ && (!usedDDWafConfigKeys .contains (DEFAULT_CONFIG_LOCATION )
278+ || currentRuleVersion == null )) {
256279 currentRuleVersion = wafDiagnostics .rulesetVersion ;
257280 statsReporter .setRulesVersion (currentRuleVersion );
258281 if (modulesToUpdateVersionIn != null ) {
@@ -282,13 +305,7 @@ private void subscribeAsmFeatures() {
282305 Product .ASM_FEATURES ,
283306 AppSecFeaturesDeserializer .INSTANCE ,
284307 (configKey , newConfig , hinter ) -> {
285- if (!hasUserWafConfig && !defaultConfigActivated ) {
286- // features activated in runtime
287- init ();
288- }
289- if (!initialized ) {
290- throw new IllegalStateException ();
291- }
308+ maybeApplyDefaultConfig ();
292309 if (newConfig == null ) {
293310 mergedAsmFeatures .removeConfig (configKey );
294311 } else {
@@ -305,10 +322,7 @@ private void subscribeAsmFeatures() {
305322
306323 private void distributeSubConfigurations (
307324 String key , AppSecModuleConfigurer .Reconfiguration reconfiguration ) {
308- if (usedDDWafConfigKeys .isEmpty () && !defaultConfigActivated && !hasUserWafConfig ) {
309- // no config left in the WAF builder, add the default config
310- init ();
311- }
325+ maybeApplyDefaultConfig ();
312326 for (Map .Entry <String , SubconfigListener > entry : subconfigListeners .entrySet ()) {
313327 SubconfigListener listener = entry .getValue ();
314328 try {
@@ -320,43 +334,56 @@ private void distributeSubConfigurations(
320334 }
321335 }
322336
337+ private void maybeApplyDefaultConfig () {
338+ if (!usedDDWafConfigKeys .isEmpty ()) {
339+ return ;
340+ }
341+ // any error here means that we are not able to fetch the default/user config so we print a
342+ // message and stop receiving RC messages as the installation is broken
343+ try {
344+ init ();
345+ } catch (Exception e ) {
346+ log .error ("Error applying default AppSec configuration; AppSec won´t work properly" , e );
347+ close ();
348+ }
349+ }
350+
323351 @ Override
324352 public void init () {
325353 Map <String , Object > wafConfig ;
326- hasUserWafConfig = false ;
327354 try {
328355 wafConfig = loadUserWafConfig (tracerConfig );
356+ wafConfigOrigin = LOCAL_STABLE_CONFIG ;
329357 } catch (Exception e ) {
330358 log .error ("Error loading user-provided config" , e );
331359 throw new AbortStartupException ("Error loading user-provided config" , e );
332360 }
333361 if (wafConfig == null ) {
334362 try {
335363 wafConfig = loadDefaultWafConfig ();
336- defaultConfigActivated = true ;
364+ wafConfigOrigin = DEFAULT ;
337365 } catch (IOException e ) {
338366 log .error ("Error loading default config" , e );
339367 throw new AbortStartupException ("Error loading default config" , e );
340368 }
341- } else {
342- hasUserWafConfig = true ;
343369 }
344- this . mergedAsmFeatures = new MergedAsmFeatures ();
345- this . initialized = true ;
370+ mergedAsmFeatures . clear ();
371+ usedDDWafConfigKeys . clear () ;
346372
347373 if (wafConfig .isEmpty ()) {
348374 throw new IllegalStateException ("Expected default waf config to be available" );
349375 }
350376 try {
351377 handleWafUpdateResultReport (DEFAULT_WAF_CONFIG_RULE , wafConfig );
378+ usedDDWafConfigKeys .add (DEFAULT_WAF_CONFIG_RULE );
352379 } catch (AppSecModule .AppSecModuleActivationException e ) {
353380 throw new RuntimeException (e );
354381 }
355382 }
356383
357384 public void maybeSubscribeConfigPolling () {
358385 if (this .configurationPoller != null ) {
359- if (hasUserWafConfig
386+ if (wafConfigOrigin == LOCAL_STABLE_CONFIG
360387 && tracerConfig .getAppSecActivation () == ProductActivation .FULLY_ENABLED ) {
361388 log .info (
362389 "AppSec will not use remote config because "
@@ -437,6 +464,15 @@ private static Map<String, Object> loadDefaultWafConfig() throws IOException {
437464 }
438465 }
439466
467+ private static boolean hasUserWafConfig (Config tracerConfig ) {
468+ String filename = tracerConfig .getAppSecRulesFile ();
469+ if (filename == null ) {
470+ return false ;
471+ }
472+ File file = Paths .get (filename ).toFile ();
473+ return file .exists () && file .isFile () && file .canRead ();
474+ }
475+
440476 private static Map <String , Object > loadUserWafConfig (Config tracerConfig ) throws IOException {
441477 log .debug ("Loading user waf config" );
442478 String filename = tracerConfig .getAppSecRulesFile ();
0 commit comments