diff --git a/dd-java-agent/appsec/build.gradle b/dd-java-agent/appsec/build.gradle index bcc36fe36d1..e7bd214110e 100644 --- a/dd-java-agent/appsec/build.gradle +++ b/dd-java-agent/appsec/build.gradle @@ -97,6 +97,9 @@ ext { 'com.datadog.appsec.config.AppSecConfig.AppSecConfigV1', 'com.datadog.appsec.config.AppSecConfig.AppSecConfigV2', 'com.datadog.appsec.config.AppSecConfig.NumberJsonAdapter', + 'com.datadog.appsec.config.AppSecFeatures', + 'com.datadog.appsec.config.AppSecFeatures.Asm', + 'com.datadog.appsec.config.AppSecFeatures.ApiSecurity', 'com.datadog.appsec.event.ReplaceableEventProducerService', ] excludedClassesBranchCoverage = [ diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java index db43c401f0d..d0eabe70bc0 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java @@ -67,18 +67,21 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s EventDispatcher eventDispatcher = new EventDispatcher(); REPLACEABLE_EVENT_PRODUCER.replaceEventProducerService(eventDispatcher); + ApiSecurityRequestSampler requestSampler = new ApiSecurityRequestSampler(config); + ConfigurationPoller configurationPoller = sco.configurationPoller(config); // may throw and abort startup APP_SEC_CONFIG_SERVICE = new AppSecConfigServiceImpl( - config, configurationPoller, () -> reloadSubscriptions(REPLACEABLE_EVENT_PRODUCER)); + config, + configurationPoller, + requestSampler, + () -> reloadSubscriptions(REPLACEABLE_EVENT_PRODUCER)); APP_SEC_CONFIG_SERVICE.init(); sco.createRemaining(config); RateLimiter rateLimiter = getRateLimiter(config, sco.monitoring); - ApiSecurityRequestSampler requestSampler = - new ApiSecurityRequestSampler(config, configurationPoller); GatewayBridge gatewayBridge = new GatewayBridge( diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/ApiSecurityRequestSampler.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/ApiSecurityRequestSampler.java index 4ebb8fde35b..38b5c631a4e 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/ApiSecurityRequestSampler.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/ApiSecurityRequestSampler.java @@ -1,19 +1,10 @@ package com.datadog.appsec.api.security; -import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE; - -import com.datadog.appsec.config.AppSecFeaturesDeserializer; -import datadog.remoteconfig.ConfigurationPoller; -import datadog.remoteconfig.Product; import datadog.trace.api.Config; import java.util.concurrent.atomic.AtomicLong; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class ApiSecurityRequestSampler { - private static final Logger log = LoggerFactory.getLogger(ApiSecurityRequestSampler.class); - private volatile int sampling; private final AtomicLong cumulativeCounter = new AtomicLong(); @@ -21,36 +12,23 @@ public ApiSecurityRequestSampler(final Config config) { sampling = computeSamplingParameter(config.getApiSecurityRequestSampleRate()); } - public ApiSecurityRequestSampler(final Config config, ConfigurationPoller configurationPoller) { - this(config); - if (configurationPoller == null) { - return; + /** + * Sets the new sampling parameter + * + * @return {@code true} if the value changed + */ + public boolean setSampling(final float newSamplingFloat) { + int newSampling = computeSamplingParameter(newSamplingFloat); + if (newSampling != sampling) { + sampling = newSampling; + cumulativeCounter.set(0); // Reset current sampling counter + return true; } + return false; + } - configurationPoller.addListener( - Product.ASM_FEATURES, - "asm_api_security", - AppSecFeaturesDeserializer.INSTANCE, - (configKey, newConfig, pollingRateHinter) -> { - if (newConfig != null && newConfig.apiSecurity != null) { - Float newSamplingFloat = newConfig.apiSecurity.requestSampleRate; - if (newSamplingFloat != null) { - int newSampling = computeSamplingParameter(newSamplingFloat); - if (newSampling != sampling) { - sampling = newSampling; - cumulativeCounter.set(0); // Reset current sampling counter - if (sampling == 0) { - log.info("Api Security is disabled via remote-config"); - } else { - log.info( - "Api Security changed via remote-config. New sampling rate is {}% of all requests.", - sampling); - } - } - } - } - }); - configurationPoller.addCapabilities(CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE); + public int getSampling() { + return sampling; } public boolean sampleRequest() { @@ -69,7 +47,7 @@ static int computeSamplingParameter(final float pct) { return 100; } if (pct < 0) { - // We don't support disabling Api Security by setting it, so we set it to 100%. + // Api security can only be disabled by setting the sampling to zero, so we set it to 100%. // TODO: We probably want a warning here. return 100; } diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java index 9c5dbc71396..749dad02597 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java @@ -12,8 +12,10 @@ import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_REQUEST_BLOCKING; import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_TRUSTED_IPS; import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_USER_BLOCKING; +import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; import com.datadog.appsec.AppSecSystem; +import com.datadog.appsec.api.security.ApiSecurityRequestSampler; import com.datadog.appsec.config.AppSecModuleConfigurer.SubconfigListener; import com.datadog.appsec.config.CurrentAppSecConfig.DirtyStatus; import com.datadog.appsec.util.AbortStartupException; @@ -34,7 +36,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +61,7 @@ public class AppSecConfigServiceImpl implements AppSecConfigService { private final Config tracerConfig; private final List traceSegmentPostProcessors = new ArrayList<>(); private final AppSecModuleConfigurer.Reconfiguration reconfiguration; + private final ApiSecurityRequestSampler apiSecurityRequestSampler; private final ConfigurationEndListener applyWAFChangesAsListener = this::applyWAFChanges; @@ -67,11 +69,13 @@ public class AppSecConfigServiceImpl implements AppSecConfigService { public AppSecConfigServiceImpl( Config tracerConfig, - @Nullable ConfigurationPoller configurationPoller, + ConfigurationPoller configurationPoller, + ApiSecurityRequestSampler apiSecurityRequestSampler, AppSecModuleConfigurer.Reconfiguration reconfig) { this.tracerConfig = tracerConfig; this.configurationPoller = configurationPoller; this.reconfiguration = reconfig; + this.apiSecurityRequestSampler = apiSecurityRequestSampler; } private void subscribeConfigurationPoller() { @@ -79,8 +83,12 @@ private void subscribeConfigurationPoller() { if (tracerConfig.getAppSecActivation() == ProductActivation.ENABLED_INACTIVE) { subscribeActivation(); } else { - log.debug("Will not subscribe to ASM_FEATURES (AppSec explicitly enabled)"); + log.debug( + "Will not subscribe to ASM_FEATURES['asm_features_activation'] (AppSec explicitly enabled)"); } + + subscribeApiSecurity(); + if (!hasUserWafConfig) { subscribeRulesAndData(); } else { @@ -164,8 +172,18 @@ private void subscribeActivation() { if (!initialized) { throw new IllegalStateException(); } - final boolean newState = - newConfig != null && newConfig.asm != null && newConfig.asm.enabled; + final boolean newState; + if (newConfig == null) { + // configuration file was removed, restore the default + newState = tracerConfig.getAppSecActivation() == ProductActivation.FULLY_ENABLED; + } else if (newConfig.asm == null || newConfig.asm.enabled == null) { + // invalid payload from the backend, restore the default + log.debug( + SEND_TELEMETRY, "Invalid 'asm_features_activation' payload : {}", newConfig.asm); + newState = tracerConfig.getAppSecActivation() == ProductActivation.FULLY_ENABLED; + } else { + newState = newConfig.asm.enabled; + } if (AppSecSystem.isActive() != newState) { log.info("AppSec {} (runtime)", newState ? "enabled" : "disabled"); AppSecSystem.setActive(newState); @@ -179,6 +197,43 @@ private void subscribeActivation() { this.configurationPoller.addCapabilities(CAPABILITY_ASM_ACTIVATION); } + private void subscribeApiSecurity() { + this.configurationPoller.addListener( + Product.ASM_FEATURES, + "asm_api_security", + AppSecFeaturesDeserializer.INSTANCE, + (configKey, newConfig, hinter) -> { + if (!initialized) { + throw new IllegalStateException(); + } + final float newSampling; + if (newConfig == null) { + // configuration file was removed on the backend, restore the default + newSampling = tracerConfig.getApiSecurityRequestSampleRate(); + } else if (newConfig.apiSecurity == null + || newConfig.apiSecurity.requestSampleRate == null) { + // invalid payload from the backend, restore the default + log.debug( + SEND_TELEMETRY, "Invalid 'asm_api_security' payload : {}", newConfig.apiSecurity); + newSampling = tracerConfig.getApiSecurityRequestSampleRate(); + } else { + newSampling = newConfig.apiSecurity.requestSampleRate; + } + + if (apiSecurityRequestSampler.setSampling(newSampling)) { + int pct = apiSecurityRequestSampler.getSampling(); + if (pct == 0) { + log.info("Api Security is disabled via remote-config"); + } else { + log.info( + "Api Security changed via remote-config. New sampling rate is {}% of all requests.", + pct); + } + } + }); + this.configurationPoller.addCapabilities(CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE); + } + private void distributeSubConfigurations( Map newConfig, AppSecModuleConfigurer.Reconfiguration reconfiguration) { for (Map.Entry entry : subconfigListeners.entrySet()) { diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecFeatures.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecFeatures.java index cba6d679e98..447c6246379 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecFeatures.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecFeatures.java @@ -7,11 +7,26 @@ public class AppSecFeatures { public ApiSecurity apiSecurity; public static class Asm { - public boolean enabled; + public Boolean enabled; + + @Override + public String toString() { + return "Asm{" + "enabled=" + enabled + '}'; + } } public static class ApiSecurity { @com.squareup.moshi.Json(name = "request_sample_rate") public Float requestSampleRate; + + @Override + public String toString() { + return "ApiSecurity{" + "requestSampleRate=" + requestSampleRate + '}'; + } + } + + @Override + public String toString() { + return "AppSecFeatures{" + "asm=" + asm + ", apiSecurity=" + apiSecurity + '}'; } } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/api/security/ApiSecurityRequestSamplerTest.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/api/security/ApiSecurityRequestSamplerTest.groovy index ec59242f4e1..ff256bc21fb 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/api/security/ApiSecurityRequestSamplerTest.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/api/security/ApiSecurityRequestSamplerTest.groovy @@ -1,10 +1,5 @@ package com.datadog.appsec.api.security -import com.datadog.appsec.config.AppSecFeatures -import com.datadog.appsec.config.AppSecFeaturesDeserializer -import datadog.remoteconfig.ConfigurationChangesTypedListener -import datadog.remoteconfig.ConfigurationPoller -import datadog.remoteconfig.Product import datadog.trace.api.Config import datadog.trace.test.util.DDSpecification import spock.lang.Shared @@ -46,31 +41,13 @@ class ApiSecurityRequestSamplerTest extends DDSpecification { -0.5 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // Wrong sample rate - use 100% } - void 'update sample rate via remote-config'() { + void 'update sample rate'() { given: - ConfigurationPoller poller = Mock() def config = Spy(Config.get()) - ConfigurationChangesTypedListener listener - AppSecFeatures newConfig = new AppSecFeatures().tap { - asm = new AppSecFeatures.Asm().tap { - enabled = true - } - apiSecurity = new AppSecFeatures.ApiSecurity().tap { - requestSampleRate = 0.2 - } - } - - when: - def sampler = new ApiSecurityRequestSampler(config, poller) - - then: - 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', AppSecFeaturesDeserializer.INSTANCE, _) >> { - listener = it[3] as ConfigurationChangesTypedListener - } - listener != null + def sampler = new ApiSecurityRequestSampler(config) when: - listener.accept(null, newConfig, null) + sampler.setSampling(0.2) then: sampler.sampling == 20 diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/config/AppSecConfigServiceImplSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/config/AppSecConfigServiceImplSpecification.groovy index a75726c2b83..eb3ec4227c3 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/config/AppSecConfigServiceImplSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/config/AppSecConfigServiceImplSpecification.groovy @@ -1,6 +1,7 @@ package com.datadog.appsec.config import com.datadog.appsec.AppSecSystem +import com.datadog.appsec.api.security.ApiSecurityRequestSampler import com.datadog.appsec.util.AbortStartupException import datadog.remoteconfig.ConfigurationChangesListener import datadog.remoteconfig.ConfigurationChangesTypedListener @@ -14,12 +15,25 @@ import datadog.trace.test.util.DDSpecification import java.nio.file.Files import java.nio.file.Path +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_ACTIVATION +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_CUSTOM_BLOCKING_RESPONSE +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_CUSTOM_RULES +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_DD_RULES +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_EXCLUSIONS +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_IP_BLOCKING +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_RASP_SQLI +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_REQUEST_BLOCKING +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_TRUSTED_IPS +import static datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.CAPABILITY_ASM_USER_BLOCKING + class AppSecConfigServiceImplSpecification extends DDSpecification { ConfigurationPoller poller = Mock() def config = Mock(Class.forName('datadog.trace.api.Config')) + ApiSecurityRequestSampler sampler = Mock(ApiSecurityRequestSampler) AppSecModuleConfigurer.Reconfiguration reconf = Stub() - AppSecConfigServiceImpl appSecConfigService = new AppSecConfigServiceImpl(config, poller, reconf) + AppSecConfigServiceImpl appSecConfigService = new AppSecConfigServiceImpl(config, poller, sampler, reconf) void cleanup() { appSecConfigService?.close() @@ -36,9 +50,11 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { 1 * config.getAppSecActivation() >> ProductActivation.ENABLED_INACTIVE 1 * poller.addListener(Product.ASM_DD, _, _) 1 * poller.addListener(Product.ASM_FEATURES, 'asm_features_activation', _, _) + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) 1 * poller.addListener(Product.ASM, _, _) 1 * poller.addListener(Product.ASM_DATA, _, _) 1 * poller.addConfigurationEndListener(_) + 1 * poller.addCapabilities(CAPABILITY_ASM_ACTIVATION) } void 'no subscription to ASM_FEATURES if appsec is fully enabled'() { @@ -51,10 +67,12 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { then: 1 * config.getAppSecActivation() >> ProductActivation.FULLY_ENABLED 1 * poller.addListener(Product.ASM_DD, _, _) + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) 1 * poller.addListener(Product.ASM, _, _) 1 * poller.addListener(Product.ASM_DATA, _, _) 1 * poller.addConfigurationEndListener(_) 0 * poller.addListener(*_) + 0 * poller.addCapabilities(CAPABILITY_ASM_ACTIVATION) } void 'no subscription to ASM_FEATURES if appsec is fully disabled'() { @@ -67,10 +85,12 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { then: 1 * config.getAppSecActivation() >> ProductActivation.FULLY_DISABLED 1 * poller.addListener(Product.ASM_DD, _, _) + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) 1 * poller.addListener(Product.ASM, _, _) 1 * poller.addListener(Product.ASM_DATA, _, _) 1 * poller.addConfigurationEndListener(_) 0 * poller.addListener(*_) + 0 * poller.addCapabilities(CAPABILITY_ASM_ACTIVATION) } void 'no subscription to ASM ASM_DD ASM_DATA if custom rules are provided'() { @@ -89,6 +109,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { then: 2 * config.getAppSecActivation() >> ProductActivation.ENABLED_INACTIVE 1 * poller.addListener(Product.ASM_FEATURES, 'asm_features_activation', _, _) + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) 1 * poller.addConfigurationEndListener(_) 0 * poller.addListener(*_) } @@ -174,8 +195,8 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { 1 * poller.addListener(Product.ASM_FEATURES, 'asm_features_activation', _, _) >> { listeners.savedFeaturesDeserializer = it[2] listeners.savedFeaturesListener = it[3] - true } + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) 1 * poller.addConfigurationEndListener(_) >> { listeners.savedConfEndListener = it[0] } _ * poller._ 0 * _._ @@ -212,7 +233,6 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { 1 * poller.addListener(Product.ASM_DD, _, _) >> { listeners.savedConfDeserializer = it[1] listeners.savedConfChangesListener = it[2] - true } 1 * poller.addListener(Product.ASM_DATA, _, _) >> { listeners.savedWafDataDeserializer = it[1] @@ -222,14 +242,23 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { listeners.savedWafRulesOverrideDeserializer = it[1] listeners.savedWafRulesOverrideListener = it[2] } - 1 * poller.addListener(Product.ASM_FEATURES, "asm_features_activation", _, _) >> { + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_features_activation', _, _) >> { listeners.savedFeaturesDeserializer = it[2] listeners.savedFeaturesListener = it[3] - true } + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) 1 * poller.addConfigurationEndListener(_) >> { listeners.savedConfEndListener = it[0] } - 1 * poller.addCapabilities(2L) - 1 * poller.addCapabilities(2099132L) + 1 * poller.addCapabilities(CAPABILITY_ASM_ACTIVATION) + 1 * poller.addCapabilities(CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE) + 1 * poller.addCapabilities(CAPABILITY_ASM_DD_RULES + | CAPABILITY_ASM_IP_BLOCKING + | CAPABILITY_ASM_EXCLUSIONS + | CAPABILITY_ASM_REQUEST_BLOCKING + | CAPABILITY_ASM_USER_BLOCKING + | CAPABILITY_ASM_CUSTOM_RULES + | CAPABILITY_ASM_CUSTOM_BLOCKING_RESPONSE + | CAPABILITY_ASM_TRUSTED_IPS + | CAPABILITY_ASM_RASP_SQLI) 0 * _._ initialWafConfig.get() != null @@ -272,16 +301,18 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { then: 1 * subconfigListener.onNewSubconfig({ CurrentAppSecConfig casc -> casc.ddConfig == AppSecConfig.valueOf([version: '2.0']) - casc.mergedUpdateConfig.rawConfig['rules_override'] == [[ + casc.mergedUpdateConfig.rawConfig['rules_override'] == [ + [ rules_target: [[rule_id: 'foo']], - enabled: false - ]] - casc.mergedAsmData == [[data:[], id: 'foo', type: '']] + enabled : false + ] + ] + casc.mergedAsmData == [[data: [], id: 'foo', type: '']] }, _) 0 * _._ when: - listeners.savedFeaturesListener.accept('config_key', + listeners.savedFeaturesListener.accept('asm_features_activation', listeners.savedFeaturesDeserializer.deserialize('{"asm":{"enabled": false}}'.bytes), ConfigurationChangesListener.PollingRateHinter.NOOP) listeners.savedConfEndListener.onConfigurationEnd() @@ -291,7 +322,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { AppSecSystem.active == false when: 'switch back to enabled' - listeners.savedFeaturesListener.accept('config_key', + listeners.savedFeaturesListener.accept('asm_features_activation', listeners.savedFeaturesDeserializer.deserialize('{"asm":{"enabled": true}}'.bytes), ConfigurationChangesListener.PollingRateHinter.NOOP) listeners.savedConfEndListener.onConfigurationEnd() @@ -300,16 +331,17 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { AppSecSystem.active == true when: 'asm are not set' - listeners.savedFeaturesListener.accept('config_key', + listeners.savedFeaturesListener.accept('asm_features_activation', listeners.savedFeaturesDeserializer.deserialize('{}'.bytes), ConfigurationChangesListener.PollingRateHinter.NOOP) listeners.savedConfEndListener.onConfigurationEnd() then: 'it is disabled ( == false)' + 1 * config.getAppSecActivation() >> ProductActivation.ENABLED_INACTIVE AppSecSystem.active == false when: 'switch back to enabled' - listeners.savedFeaturesListener.accept('config_key', + listeners.savedFeaturesListener.accept('asm_features_activation', listeners.savedFeaturesDeserializer.deserialize('{"asm":{"enabled": true}}'.bytes), ConfigurationChangesListener.PollingRateHinter.NOOP) listeners.savedConfEndListener.onConfigurationEnd() @@ -318,7 +350,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { AppSecSystem.active == true when: 'asm features are not set' - listeners.savedFeaturesListener.accept('config_key', + listeners.savedFeaturesListener.accept('asm_features_activation', null, ConfigurationChangesListener.PollingRateHinter.NOOP) listeners.savedConfEndListener.onConfigurationEnd() @@ -349,7 +381,6 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { 1 * poller.addListener(Product.ASM_DD, _, _) >> { listeners.savedConfDeserializer = it[1] listeners.savedConfChangesListener = it[2] - true } 1 * poller.addListener(Product.ASM_DATA, _, _) >> { listeners.savedWafDataDeserializer = it[1] @@ -359,14 +390,23 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { listeners.savedWafRulesOverrideDeserializer = it[1] listeners.savedWafRulesOverrideListener = it[2] } - 1 * poller.addListener(Product.ASM_FEATURES, "asm_features_activation", _, _) >> { + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_features_activation', _, _) >> { listeners.savedFeaturesDeserializer = it[2] listeners.savedFeaturesListener = it[3] - true } + 1 * poller.addListener(Product.ASM_FEATURES, _, _, _) 1 * poller.addConfigurationEndListener(_) >> { listeners.savedConfEndListener = it[0] } - 1 * poller.addCapabilities(2L) - 1 * poller.addCapabilities(2099132L) + 1 * poller.addCapabilities(CAPABILITY_ASM_ACTIVATION) + 1 * poller.addCapabilities(CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE) + 1 * poller.addCapabilities(CAPABILITY_ASM_DD_RULES + | CAPABILITY_ASM_IP_BLOCKING + | CAPABILITY_ASM_EXCLUSIONS + | CAPABILITY_ASM_REQUEST_BLOCKING + | CAPABILITY_ASM_USER_BLOCKING + | CAPABILITY_ASM_CUSTOM_RULES + | CAPABILITY_ASM_CUSTOM_BLOCKING_RESPONSE + | CAPABILITY_ASM_TRUSTED_IPS + | CAPABILITY_ASM_RASP_SQLI) 0 * _._ when: @@ -422,7 +462,17 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { poller = null then: - 1 * poller.removeCapabilities(2101182L) + 1 * poller.removeCapabilities(CAPABILITY_ASM_ACTIVATION + | CAPABILITY_ASM_DD_RULES + | CAPABILITY_ASM_IP_BLOCKING + | CAPABILITY_ASM_EXCLUSIONS + | CAPABILITY_ASM_REQUEST_BLOCKING + | CAPABILITY_ASM_USER_BLOCKING + | CAPABILITY_ASM_CUSTOM_RULES + | CAPABILITY_ASM_CUSTOM_BLOCKING_RESPONSE + | CAPABILITY_ASM_TRUSTED_IPS + | CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE + | CAPABILITY_ASM_RASP_SQLI) 4 * poller.removeListeners(_) 1 * poller.removeConfigurationEndListener(_) 1 * poller.stop() @@ -445,4 +495,31 @@ class AppSecConfigServiceImplSpecification extends DDSpecification { then: thrown IOException } + + void 'update sample rate via remote-config'() { + given: + def newConfig = new AppSecFeatures().tap { features -> + features.apiSecurity = new AppSecFeatures.ApiSecurity().tap { api -> + api.requestSampleRate = 0.2 + } + } + def listeners = new SavedListeners() + + when: + appSecConfigService.init() + appSecConfigService.maybeSubscribeConfigPolling() + + then: + 1 * poller.addListener(Product.ASM_FEATURES, 'asm_api_security', _, _) >> { + listeners.savedFeaturesDeserializer = it[2] + listeners.savedFeaturesListener = it[3] + } + 1 * poller.addCapabilities(CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE) + + when: + listeners.savedFeaturesListener.accept('asm_api_security', newConfig, null) + + then: + 1 * sampler.setSampling(0.2F) + } }