Control enforcement of unsealed sender rate limits via dynamic configuration.

This commit is contained in:
Jon Chambers 2021-02-12 14:10:10 -05:00 committed by Jon Chambers
parent 6332552346
commit 5f49772ca6
6 changed files with 82 additions and 24 deletions

View File

@ -392,7 +392,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
AttachmentControllerV2 attachmentControllerV2 = new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket());
AttachmentControllerV3 attachmentControllerV3 = new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey());
KeysController keysController = new KeysController(rateLimiters, keysDynamoDb, accountsManager, directoryQueue);
MessageController messageController = new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, apnFallbackManager);
MessageController messageController = new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, apnFallbackManager, dynamicConfigurationManager);
ProfileController profileController = new ProfileController(rateLimiters, accountsManager, profilesManager, usernamesManager, cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, isZkEnabled);
StickerController stickerController = new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket());
RemoteConfigController remoteConfigController = new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig());

View File

@ -22,6 +22,10 @@ public class DynamicConfiguration {
@Valid
private DynamicRemoteDeprecationConfiguration remoteDeprecation = new DynamicRemoteDeprecationConfiguration();
@JsonProperty
@Valid
private DynamicMessageRateConfiguration messageRate = new DynamicMessageRateConfiguration();
@JsonProperty
private Set<String> featureFlags = Collections.emptySet();
@ -37,6 +41,10 @@ public class DynamicConfiguration {
return remoteDeprecation;
}
public DynamicMessageRateConfiguration getMessageRateConfiguration() {
return messageRate;
}
public Set<String> getActiveFeatureFlags() {
return featureFlags;
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DynamicMessageRateConfiguration {
@JsonProperty
private boolean enforceUnsealedSenderRateLimit = false;
public boolean isEnforceUnsealedSenderRateLimit() {
return enforceUnsealedSenderRateLimit;
}
}

View File

@ -59,6 +59,7 @@ import org.whispersystems.textsecuregcm.redis.RedisOperation;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.util.Base64;
import org.whispersystems.textsecuregcm.util.Constants;
@ -79,12 +80,13 @@ public class MessageController {
private final Timer sendMessageInternalTimer = metricRegistry.timer(name(getClass(), "sendMessageInternal"));
private final Histogram outgoingMessageListSizeHistogram = metricRegistry.histogram(name(getClass(), "outgoingMessageListSize"));
private final RateLimiters rateLimiters;
private final MessageSender messageSender;
private final ReceiptSender receiptSender;
private final AccountsManager accountsManager;
private final MessagesManager messagesManager;
private final ApnFallbackManager apnFallbackManager;
private final RateLimiters rateLimiters;
private final MessageSender messageSender;
private final ReceiptSender receiptSender;
private final AccountsManager accountsManager;
private final MessagesManager messagesManager;
private final ApnFallbackManager apnFallbackManager;
private final DynamicConfigurationManager dynamicConfigurationManager;
private static final String SENT_MESSAGE_COUNTER_NAME = name(MessageController.class, "sentMessages");
private static final String REJECT_UNSEALED_SENDER_COUNTER_NAME = name(MessageController.class, "rejectUnsealedSenderLimit");
@ -102,14 +104,16 @@ public class MessageController {
ReceiptSender receiptSender,
AccountsManager accountsManager,
MessagesManager messagesManager,
ApnFallbackManager apnFallbackManager)
ApnFallbackManager apnFallbackManager,
DynamicConfigurationManager dynamicConfigurationManager)
{
this.rateLimiters = rateLimiters;
this.messageSender = messageSender;
this.receiptSender = receiptSender;
this.accountsManager = accountsManager;
this.messagesManager = messagesManager;
this.apnFallbackManager = apnFallbackManager;
this.rateLimiters = rateLimiters;
this.messageSender = messageSender;
this.receiptSender = receiptSender;
this.accountsManager = accountsManager;
this.messagesManager = messagesManager;
this.apnFallbackManager = apnFallbackManager;
this.dynamicConfigurationManager = dynamicConfigurationManager;
}
@Timed
@ -134,6 +138,10 @@ public class MessageController {
} catch (RateLimitExceededException e) {
Metrics.counter(REJECT_UNSEALED_SENDER_COUNTER_NAME, SENDER_COUNTRY_TAG_NAME, Util.getCountryCode(source.get().getNumber())).increment();
logger.debug("Rejected unsealed sender limit from: {}", source.get().getNumber());
if (dynamicConfigurationManager.getConfiguration().getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit()) {
throw e;
}
}
}

View File

@ -107,6 +107,28 @@ public class DynamicConfigurationTest {
}
}
@Test
public void testParseMessageRateConfiguration() throws JsonProcessingException {
{
final String emptyConfigYaml = "test: true";
final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER
.readValue(emptyConfigYaml, DynamicConfiguration.class);
assertFalse(emptyConfig.getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit());
}
{
final String messageRateConfigYaml =
"messageRate:\n" +
" enforceUnsealedSenderRateLimit: true";
final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER
.readValue(messageRateConfigYaml, DynamicConfiguration.class);
assertTrue(emptyConfig.getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit());
}
}
@Test
public void testParseFeatureFlags() throws JsonProcessingException {
{

View File

@ -66,6 +66,7 @@ import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.Base64;
@ -78,16 +79,17 @@ public class MessageControllerTest {
private static final String MULTI_DEVICE_RECIPIENT = "+14152222222";
private static final UUID MULTI_DEVICE_UUID = UUID.randomUUID();
private final MessageSender messageSender = mock(MessageSender.class);
private final ReceiptSender receiptSender = mock(ReceiptSender.class);
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final MessagesManager messagesManager = mock(MessagesManager.class);
private final RateLimiters rateLimiters = mock(RateLimiters.class);
private final RateLimiter rateLimiter = mock(RateLimiter.class);
private final CardinalityRateLimiter unsealedSenderLimiter = mock(CardinalityRateLimiter.class);
private final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class);
private final MessageSender messageSender = mock(MessageSender.class);
private final ReceiptSender receiptSender = mock(ReceiptSender.class);
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final MessagesManager messagesManager = mock(MessagesManager.class);
private final RateLimiters rateLimiters = mock(RateLimiters.class);
private final RateLimiter rateLimiter = mock(RateLimiter.class);
private final CardinalityRateLimiter unsealedSenderLimiter = mock(CardinalityRateLimiter.class);
private final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class);
private final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
private final ObjectMapper mapper = new ObjectMapper();
private final ObjectMapper mapper = new ObjectMapper();
@Rule
public final ResourceTestRule resources = ResourceTestRule.builder()
@ -95,7 +97,7 @@ public class MessageControllerTest {
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)))
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new MessageController(rateLimiters, messageSender, receiptSender, accountsManager,
messagesManager, apnFallbackManager))
messagesManager, apnFallbackManager, dynamicConfigurationManager))
.build();