Control enforcement of unsealed sender rate limits via dynamic configuration.
This commit is contained in:
parent
6332552346
commit
5f49772ca6
|
@ -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());
|
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());
|
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);
|
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);
|
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());
|
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());
|
RemoteConfigController remoteConfigController = new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig());
|
||||||
|
|
|
@ -22,6 +22,10 @@ public class DynamicConfiguration {
|
||||||
@Valid
|
@Valid
|
||||||
private DynamicRemoteDeprecationConfiguration remoteDeprecation = new DynamicRemoteDeprecationConfiguration();
|
private DynamicRemoteDeprecationConfiguration remoteDeprecation = new DynamicRemoteDeprecationConfiguration();
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Valid
|
||||||
|
private DynamicMessageRateConfiguration messageRate = new DynamicMessageRateConfiguration();
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Set<String> featureFlags = Collections.emptySet();
|
private Set<String> featureFlags = Collections.emptySet();
|
||||||
|
|
||||||
|
@ -37,6 +41,10 @@ public class DynamicConfiguration {
|
||||||
return remoteDeprecation;
|
return remoteDeprecation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicMessageRateConfiguration getMessageRateConfiguration() {
|
||||||
|
return messageRate;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<String> getActiveFeatureFlags() {
|
public Set<String> getActiveFeatureFlags() {
|
||||||
return featureFlags;
|
return featureFlags;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ import org.whispersystems.textsecuregcm.redis.RedisOperation;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
import org.whispersystems.textsecuregcm.util.Constants;
|
||||||
|
@ -79,12 +80,13 @@ public class MessageController {
|
||||||
private final Timer sendMessageInternalTimer = metricRegistry.timer(name(getClass(), "sendMessageInternal"));
|
private final Timer sendMessageInternalTimer = metricRegistry.timer(name(getClass(), "sendMessageInternal"));
|
||||||
private final Histogram outgoingMessageListSizeHistogram = metricRegistry.histogram(name(getClass(), "outgoingMessageListSize"));
|
private final Histogram outgoingMessageListSizeHistogram = metricRegistry.histogram(name(getClass(), "outgoingMessageListSize"));
|
||||||
|
|
||||||
private final RateLimiters rateLimiters;
|
private final RateLimiters rateLimiters;
|
||||||
private final MessageSender messageSender;
|
private final MessageSender messageSender;
|
||||||
private final ReceiptSender receiptSender;
|
private final ReceiptSender receiptSender;
|
||||||
private final AccountsManager accountsManager;
|
private final AccountsManager accountsManager;
|
||||||
private final MessagesManager messagesManager;
|
private final MessagesManager messagesManager;
|
||||||
private final ApnFallbackManager apnFallbackManager;
|
private final ApnFallbackManager apnFallbackManager;
|
||||||
|
private final DynamicConfigurationManager dynamicConfigurationManager;
|
||||||
|
|
||||||
private static final String SENT_MESSAGE_COUNTER_NAME = name(MessageController.class, "sentMessages");
|
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");
|
private static final String REJECT_UNSEALED_SENDER_COUNTER_NAME = name(MessageController.class, "rejectUnsealedSenderLimit");
|
||||||
|
@ -102,14 +104,16 @@ public class MessageController {
|
||||||
ReceiptSender receiptSender,
|
ReceiptSender receiptSender,
|
||||||
AccountsManager accountsManager,
|
AccountsManager accountsManager,
|
||||||
MessagesManager messagesManager,
|
MessagesManager messagesManager,
|
||||||
ApnFallbackManager apnFallbackManager)
|
ApnFallbackManager apnFallbackManager,
|
||||||
|
DynamicConfigurationManager dynamicConfigurationManager)
|
||||||
{
|
{
|
||||||
this.rateLimiters = rateLimiters;
|
this.rateLimiters = rateLimiters;
|
||||||
this.messageSender = messageSender;
|
this.messageSender = messageSender;
|
||||||
this.receiptSender = receiptSender;
|
this.receiptSender = receiptSender;
|
||||||
this.accountsManager = accountsManager;
|
this.accountsManager = accountsManager;
|
||||||
this.messagesManager = messagesManager;
|
this.messagesManager = messagesManager;
|
||||||
this.apnFallbackManager = apnFallbackManager;
|
this.apnFallbackManager = apnFallbackManager;
|
||||||
|
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
|
@ -134,6 +138,10 @@ public class MessageController {
|
||||||
} catch (RateLimitExceededException e) {
|
} catch (RateLimitExceededException e) {
|
||||||
Metrics.counter(REJECT_UNSEALED_SENDER_COUNTER_NAME, SENDER_COUNTRY_TAG_NAME, Util.getCountryCode(source.get().getNumber())).increment();
|
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());
|
logger.debug("Rejected unsealed sender limit from: {}", source.get().getNumber());
|
||||||
|
|
||||||
|
if (dynamicConfigurationManager.getConfiguration().getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit()) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
@Test
|
||||||
public void testParseFeatureFlags() throws JsonProcessingException {
|
public void testParseFeatureFlags() throws JsonProcessingException {
|
||||||
{
|
{
|
||||||
|
|
|
@ -66,6 +66,7 @@ import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
|
@ -78,16 +79,17 @@ public class MessageControllerTest {
|
||||||
private static final String MULTI_DEVICE_RECIPIENT = "+14152222222";
|
private static final String MULTI_DEVICE_RECIPIENT = "+14152222222";
|
||||||
private static final UUID MULTI_DEVICE_UUID = UUID.randomUUID();
|
private static final UUID MULTI_DEVICE_UUID = UUID.randomUUID();
|
||||||
|
|
||||||
private final MessageSender messageSender = mock(MessageSender.class);
|
private final MessageSender messageSender = mock(MessageSender.class);
|
||||||
private final ReceiptSender receiptSender = mock(ReceiptSender.class);
|
private final ReceiptSender receiptSender = mock(ReceiptSender.class);
|
||||||
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||||
private final MessagesManager messagesManager = mock(MessagesManager.class);
|
private final MessagesManager messagesManager = mock(MessagesManager.class);
|
||||||
private final RateLimiters rateLimiters = mock(RateLimiters.class);
|
private final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||||
private final RateLimiter rateLimiter = mock(RateLimiter.class);
|
private final RateLimiter rateLimiter = mock(RateLimiter.class);
|
||||||
private final CardinalityRateLimiter unsealedSenderLimiter = mock(CardinalityRateLimiter.class);
|
private final CardinalityRateLimiter unsealedSenderLimiter = mock(CardinalityRateLimiter.class);
|
||||||
private final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.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
|
@Rule
|
||||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||||
|
@ -95,7 +97,7 @@ public class MessageControllerTest {
|
||||||
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)))
|
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)))
|
||||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||||
.addResource(new MessageController(rateLimiters, messageSender, receiptSender, accountsManager,
|
.addResource(new MessageController(rateLimiters, messageSender, receiptSender, accountsManager,
|
||||||
messagesManager, apnFallbackManager))
|
messagesManager, apnFallbackManager, dynamicConfigurationManager))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue