Retire the legacy "abusive hosts" system in favor of newer tools

This commit is contained in:
Jon Chambers 2022-10-28 15:16:51 -04:00 committed by Jon Chambers
parent 4f8aa2eee2
commit e8ee4b50ff
6 changed files with 13 additions and 284 deletions

View File

@ -163,7 +163,6 @@ import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
import org.whispersystems.textsecuregcm.storage.AccountCleaner; import org.whispersystems.textsecuregcm.storage.AccountCleaner;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache;
@ -457,7 +456,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator( ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true); config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
AbusiveHostRules abusiveHostRules = new AbusiveHostRules(rateLimitersCluster, dynamicConfigurationManager);
RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient(config.getRegistrationServiceConfiguration().getHost(), config.getRegistrationServiceConfiguration().getPort(), config.getRegistrationServiceConfiguration().getApiKey(), config.getRegistrationServiceConfiguration().getRegistrationCaCertificate(), registrationCallbackExecutor); RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient(config.getRegistrationServiceConfiguration().getHost(), config.getRegistrationServiceConfiguration().getPort(), config.getRegistrationServiceConfiguration().getApiKey(), config.getRegistrationServiceConfiguration().getRegistrationCaCertificate(), registrationCallbackExecutor);
SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration()); SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration());
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration()); SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration());
@ -653,7 +651,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
// these should be common, but use @Auth DisabledPermittedAccount, which isnt supported yet on websocket // these should be common, but use @Auth DisabledPermittedAccount, which isnt supported yet on websocket
environment.jersey().register( environment.jersey().register(
new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters, new AccountController(pendingAccountsManager, accountsManager, rateLimiters,
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(), registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
recaptchaClient, pushNotificationManager, changeNumberManager, backupCredentialsGenerator, recaptchaClient, pushNotificationManager, changeNumberManager, backupCredentialsGenerator,
clientPresenceManager, clock)); clientPresenceManager, clock));

View File

@ -91,7 +91,6 @@ import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.registration.ClientType; import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport; import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient; import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
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.ChangeNumberManager; import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
@ -115,8 +114,6 @@ public class AccountController {
private final Logger logger = LoggerFactory.getLogger(AccountController.class); private final Logger logger = LoggerFactory.getLogger(AccountController.class);
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
private final Meter blockedHostMeter = metricRegistry.meter(name(AccountController.class, "blocked_host" ));
private final Meter countryFilterApplicable = metricRegistry.meter(name(AccountController.class, "country_filter_applicable"));
private final Meter countryFilteredHostMeter = metricRegistry.meter(name(AccountController.class, "country_limited_host" )); private final Meter countryFilteredHostMeter = metricRegistry.meter(name(AccountController.class, "country_limited_host" ));
private final Meter rateLimitedHostMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_host" )); private final Meter rateLimitedHostMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_host" ));
private final Meter rateLimitedPrefixMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_prefix" )); private final Meter rateLimitedPrefixMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_prefix" ));
@ -150,7 +147,6 @@ public class AccountController {
private final StoredVerificationCodeManager pendingAccounts; private final StoredVerificationCodeManager pendingAccounts;
private final AccountsManager accounts; private final AccountsManager accounts;
private final AbusiveHostRules abusiveHostRules;
private final RateLimiters rateLimiters; private final RateLimiters rateLimiters;
private final RegistrationServiceClient registrationServiceClient; private final RegistrationServiceClient registrationServiceClient;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
@ -171,7 +167,6 @@ public class AccountController {
public AccountController( public AccountController(
StoredVerificationCodeManager pendingAccounts, StoredVerificationCodeManager pendingAccounts,
AccountsManager accounts, AccountsManager accounts,
AbusiveHostRules abusiveHostRules,
RateLimiters rateLimiters, RateLimiters rateLimiters,
RegistrationServiceClient registrationServiceClient, RegistrationServiceClient registrationServiceClient,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager, DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
@ -186,7 +181,6 @@ public class AccountController {
) { ) {
this.pendingAccounts = pendingAccounts; this.pendingAccounts = pendingAccounts;
this.accounts = accounts; this.accounts = accounts;
this.abusiveHostRules = abusiveHostRules;
this.rateLimiters = rateLimiters; this.rateLimiters = rateLimiters;
this.registrationServiceClient = registrationServiceClient; this.registrationServiceClient = registrationServiceClient;
this.dynamicConfigurationManager = dynamicConfigurationManager; this.dynamicConfigurationManager = dynamicConfigurationManager;
@ -204,7 +198,6 @@ public class AccountController {
public AccountController( public AccountController(
StoredVerificationCodeManager pendingAccounts, StoredVerificationCodeManager pendingAccounts,
AccountsManager accounts, AccountsManager accounts,
AbusiveHostRules abusiveHostRules,
RateLimiters rateLimiters, RateLimiters rateLimiters,
RegistrationServiceClient registrationServiceClient, RegistrationServiceClient registrationServiceClient,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager, DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
@ -215,7 +208,7 @@ public class AccountController {
ChangeNumberManager changeNumberManager, ChangeNumberManager changeNumberManager,
ExternalServiceCredentialGenerator backupServiceCredentialGenerator ExternalServiceCredentialGenerator backupServiceCredentialGenerator
) { ) {
this(pendingAccounts, accounts, abusiveHostRules, rateLimiters, this(pendingAccounts, accounts, rateLimiters,
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, testDevices, recaptchaClient, registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, testDevices, recaptchaClient,
pushNotificationManager, changeNumberManager, pushNotificationManager, changeNumberManager,
backupServiceCredentialGenerator, null, Clock.systemUTC()); backupServiceCredentialGenerator, null, Clock.systemUTC());
@ -886,26 +879,12 @@ public class AccountController {
boolean countryFiltered = captchaConfig.getSignupCountryCodes().contains(countryCode) || boolean countryFiltered = captchaConfig.getSignupCountryCodes().contains(countryCode) ||
captchaConfig.getSignupRegions().contains(region); captchaConfig.getSignupRegions().contains(region);
if (abusiveHostRules.isBlocked(sourceHost)) {
blockedHostMeter.mark();
logger.info("Blocked host: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor);
if (countryFiltered) {
// this host was caught in the abusiveHostRules filter, but
// would be caught by country filter as well
countryFilterApplicable.mark();
}
return true;
}
try { try {
rateLimiters.getSmsVoiceIpLimiter().validate(sourceHost); rateLimiters.getSmsVoiceIpLimiter().validate(sourceHost);
} catch (RateLimitExceededException e) { } catch (RateLimitExceededException e) {
logger.info("Rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor); logger.info("Rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor);
rateLimitedHostMeter.mark(); rateLimitedHostMeter.mark();
if (shouldAutoBlock(sourceHost)) {
logger.info("Auto-block: {}", sourceHost);
abusiveHostRules.setBlockedHost(sourceHost);
}
return true; return true;
} }
@ -914,10 +893,7 @@ public class AccountController {
} catch (RateLimitExceededException e) { } catch (RateLimitExceededException e) {
logger.info("Prefix rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor); logger.info("Prefix rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor);
rateLimitedPrefixMeter.mark(); rateLimitedPrefixMeter.mark();
if (shouldAutoBlock(sourceHost)) {
logger.info("Auto-block: {}", sourceHost);
abusiveHostRules.setBlockedHost(sourceHost);
}
return true; return true;
} }
@ -925,6 +901,7 @@ public class AccountController {
countryFilteredHostMeter.mark(); countryFilteredHostMeter.mark();
return true; return true;
} }
return false; return false;
} }
@ -944,16 +921,6 @@ public class AccountController {
} }
} }
private boolean shouldAutoBlock(String sourceHost) {
try {
rateLimiters.getAutoBlockLimiter().validate(sourceHost);
} catch (RateLimitExceededException e) {
return true;
}
return false;
}
private String generatePushChallenge() { private String generatePushChallenge() {
SecureRandom random = new SecureRandom(); SecureRandom random = new SecureRandom();
byte[] challenge = new byte[16]; byte[] challenge = new byte[16];

View File

@ -15,7 +15,6 @@ public class RateLimiters {
private final RateLimiter voiceDestinationDailyLimiter; private final RateLimiter voiceDestinationDailyLimiter;
private final RateLimiter smsVoiceIpLimiter; private final RateLimiter smsVoiceIpLimiter;
private final RateLimiter smsVoicePrefixLimiter; private final RateLimiter smsVoicePrefixLimiter;
private final RateLimiter autoBlockLimiter;
private final RateLimiter verifyLimiter; private final RateLimiter verifyLimiter;
private final RateLimiter pinLimiter; private final RateLimiter pinLimiter;
@ -60,10 +59,6 @@ public class RateLimiters {
config.getSmsVoicePrefix().getBucketSize(), config.getSmsVoicePrefix().getBucketSize(),
config.getSmsVoicePrefix().getLeakRatePerMinute()); config.getSmsVoicePrefix().getLeakRatePerMinute());
this.autoBlockLimiter = new RateLimiter(cacheCluster, "autoBlock",
config.getAutoBlock().getBucketSize(),
config.getAutoBlock().getLeakRatePerMinute());
this.verifyLimiter = new LockingRateLimiter(cacheCluster, "verify", this.verifyLimiter = new LockingRateLimiter(cacheCluster, "verify",
config.getVerifyNumber().getBucketSize(), config.getVerifyNumber().getBucketSize(),
config.getVerifyNumber().getLeakRatePerMinute()); config.getVerifyNumber().getLeakRatePerMinute());
@ -158,10 +153,6 @@ public class RateLimiters {
return smsVoicePrefixLimiter; return smsVoicePrefixLimiter;
} }
public RateLimiter getAutoBlockLimiter() {
return autoBlockLimiter;
}
public RateLimiter getVoiceDestinationLimiter() { public RateLimiter getVoiceDestinationLimiter() {
return voiceDestinationLimiter; return voiceDestinationLimiter;
} }

View File

@ -1,52 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import static com.codahale.metrics.MetricRegistry.name;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
import java.time.Duration;
import com.google.common.annotations.VisibleForTesting;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.util.Constants;
public class AbusiveHostRules {
private static final String KEY_PREFIX = "abusive_hosts::";
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
private final Timer getTimer = metricRegistry.timer(name(AbusiveHostRules.class, "get"));
private final Timer insertTimer = metricRegistry.timer(name(AbusiveHostRules.class, "setBlockedHost"));
private final FaultTolerantRedisCluster redisCluster;
private final DynamicConfigurationManager<DynamicConfiguration> configurationManager;
public AbusiveHostRules(FaultTolerantRedisCluster redisCluster, final DynamicConfigurationManager<DynamicConfiguration> configurationManager) {
this.redisCluster = redisCluster;
this.configurationManager = configurationManager;
}
public boolean isBlocked(String host) {
try (Timer.Context timer = getTimer.time()) {
return this.redisCluster.withCluster(connection -> connection.sync().exists(prefix(host))) > 0;
}
}
public void setBlockedHost(String host) {
Duration expireTime = configurationManager.getConfiguration().getAbusiveHostRules().getExpirationTime();
try (Timer.Context timer = insertTimer.time()) {
this.redisCluster.useCluster(connection -> connection.sync().setex(prefix(host), expireTime.toSeconds(), "1"));
}
}
@VisibleForTesting
public static String prefix(String keyName) {
return KEY_PREFIX + keyName;
}
}

View File

@ -22,7 +22,6 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -100,7 +99,6 @@ import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.registration.ClientType; import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport; import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient; import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
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.ChangeNumberManager; import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
@ -134,7 +132,6 @@ class AccountControllerTest {
private static final UUID SENDER_TRANSFER_UUID = UUID.randomUUID(); private static final UUID SENDER_TRANSFER_UUID = UUID.randomUUID();
private static final UUID RESERVATION_TOKEN = UUID.randomUUID(); private static final UUID RESERVATION_TOKEN = UUID.randomUUID();
private static final String ABUSIVE_HOST = "192.168.1.1";
private static final String NICE_HOST = "127.0.0.1"; private static final String NICE_HOST = "127.0.0.1";
private static final String RATE_LIMITED_IP_HOST = "10.0.0.1"; private static final String RATE_LIMITED_IP_HOST = "10.0.0.1";
private static final String RATE_LIMITED_PREFIX_HOST = "10.0.0.2"; private static final String RATE_LIMITED_PREFIX_HOST = "10.0.0.2";
@ -147,7 +144,6 @@ class AccountControllerTest {
private static StoredVerificationCodeManager pendingAccountsManager = mock(StoredVerificationCodeManager.class); private static StoredVerificationCodeManager pendingAccountsManager = mock(StoredVerificationCodeManager.class);
private static AccountsManager accountsManager = mock(AccountsManager.class); private static AccountsManager accountsManager = mock(AccountsManager.class);
private static AbusiveHostRules abusiveHostRules = mock(AbusiveHostRules.class);
private static RateLimiters rateLimiters = mock(RateLimiters.class); private static RateLimiters rateLimiters = mock(RateLimiters.class);
private static RateLimiter rateLimiter = mock(RateLimiter.class); private static RateLimiter rateLimiter = mock(RateLimiter.class);
private static RateLimiter pinLimiter = mock(RateLimiter.class); private static RateLimiter pinLimiter = mock(RateLimiter.class);
@ -187,7 +183,6 @@ class AccountControllerTest {
.setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new AccountController(pendingAccountsManager, .addResource(new AccountController(pendingAccountsManager,
accountsManager, accountsManager,
abusiveHostRules,
rateLimiters, rateLimiters,
registrationServiceClient, registrationServiceClient,
dynamicConfigurationManager, dynamicConfigurationManager,
@ -218,7 +213,6 @@ class AccountControllerTest {
when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter); when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter);
when(rateLimiters.getSmsVoiceIpLimiter()).thenReturn(smsVoiceIpLimiter); when(rateLimiters.getSmsVoiceIpLimiter()).thenReturn(smsVoiceIpLimiter);
when(rateLimiters.getSmsVoicePrefixLimiter()).thenReturn(smsVoicePrefixLimiter); when(rateLimiters.getSmsVoicePrefixLimiter()).thenReturn(smsVoicePrefixLimiter);
when(rateLimiters.getAutoBlockLimiter()).thenReturn(autoBlockLimiter);
when(rateLimiters.getUsernameSetLimiter()).thenReturn(usernameSetLimiter); when(rateLimiters.getUsernameSetLimiter()).thenReturn(usernameSetLimiter);
when(rateLimiters.getUsernameReserveLimiter()).thenReturn(usernameReserveLimiter); when(rateLimiters.getUsernameReserveLimiter()).thenReturn(usernameReserveLimiter);
when(rateLimiters.getUsernameLookupLimiter()).thenReturn(usernameLookupLimiter); when(rateLimiters.getUsernameLookupLimiter()).thenReturn(usernameLookupLimiter);
@ -303,9 +297,6 @@ class AccountControllerTest {
when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig); when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig);
} }
when(abusiveHostRules.isBlocked(eq(ABUSIVE_HOST))).thenReturn(true);
when(abusiveHostRules.isBlocked(eq(NICE_HOST))).thenReturn(false);
when(recaptchaClient.verify(eq(INVALID_CAPTCHA_TOKEN), anyString())) when(recaptchaClient.verify(eq(INVALID_CAPTCHA_TOKEN), anyString()))
.thenReturn(RecaptchaClient.AssessmentResult.invalid()); .thenReturn(RecaptchaClient.AssessmentResult.invalid());
when(recaptchaClient.verify(eq(VALID_CAPTCHA_TOKEN), anyString())) when(recaptchaClient.verify(eq(VALID_CAPTCHA_TOKEN), anyString()))
@ -313,9 +304,6 @@ class AccountControllerTest {
doThrow(new RateLimitExceededException(Duration.ZERO)).when(pinLimiter).validate(eq(SENDER_OVER_PIN)); doThrow(new RateLimitExceededException(Duration.ZERO)).when(pinLimiter).validate(eq(SENDER_OVER_PIN));
doThrow(new RateLimitExceededException(Duration.ZERO)).when(autoBlockLimiter).validate(eq(RATE_LIMITED_PREFIX_HOST));
doThrow(new RateLimitExceededException(Duration.ZERO)).when(autoBlockLimiter).validate(eq(RATE_LIMITED_IP_HOST));
doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoicePrefixLimiter).validate(SENDER_OVER_PREFIX.substring(0, 4+2)); doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoicePrefixLimiter).validate(SENDER_OVER_PREFIX.substring(0, 4+2));
doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_IP_HOST); doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_IP_HOST);
doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_HOST2); doThrow(new RateLimitExceededException(Duration.ZERO)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_HOST2);
@ -326,13 +314,11 @@ class AccountControllerTest {
reset( reset(
pendingAccountsManager, pendingAccountsManager,
accountsManager, accountsManager,
abusiveHostRules,
rateLimiters, rateLimiters,
rateLimiter, rateLimiter,
pinLimiter, pinLimiter,
smsVoiceIpLimiter, smsVoiceIpLimiter,
smsVoicePrefixLimiter, smsVoicePrefixLimiter,
autoBlockLimiter,
usernameSetLimiter, usernameSetLimiter,
usernameReserveLimiter, usernameReserveLimiter,
usernameLookupLimiter, usernameLookupLimiter,
@ -489,7 +475,6 @@ class AccountControllerTest {
final Phonenumber.PhoneNumber expectedPhoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null); final Phonenumber.PhoneNumber expectedPhoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verify(registrationServiceClient).sendRegistrationCode(expectedPhoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT); verify(registrationServiceClient).sendRegistrationCode(expectedPhoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
verify(pendingAccountsManager).store(eq(SENDER), argThat( verify(pendingAccountsManager).store(eq(SENDER), argThat(
storedVerificationCode -> Arrays.equals(storedVerificationCode.sessionId(), sessionId) && storedVerificationCode -> Arrays.equals(storedVerificationCode.sessionId(), sessionId) &&
"1234-push".equals(storedVerificationCode.pushCode()))); "1234-push".equals(storedVerificationCode.pushCode())));
@ -550,7 +535,6 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.VOICE, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT); verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.VOICE, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
} }
@Test @Test
@ -572,7 +556,6 @@ class AccountControllerTest {
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER_PREAUTH, null); final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER_PREAUTH, null);
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT); verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
} }
@Test @Test
@ -588,7 +571,6 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(403); assertThat(response.getStatus()).isEqualTo(403);
verifyNoInteractions(registrationServiceClient); verifyNoInteractions(registrationServiceClient);
verifyNoInteractions(abusiveHostRules);
} }
@Test @Test
@ -649,24 +631,7 @@ class AccountControllerTest {
} }
@Test @Test
void testSendAbusiveHost() { void testSendWithValidCaptcha() throws NumberParseException {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(ABUSIVE_HOST));
verifyNoInteractions(registrationServiceClient);
}
@Test
void testSendAbusiveHostWithValidCaptcha() throws NumberParseException {
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any())) when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(new byte[16])); .thenReturn(CompletableFuture.completedFuture(new byte[16]));
@ -676,38 +641,36 @@ class AccountControllerTest {
.target(String.format("/v1/accounts/sms/code/%s", SENDER)) .target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("captcha", VALID_CAPTCHA_TOKEN) .queryParam("captcha", VALID_CAPTCHA_TOKEN)
.request() .request()
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST) .header("X-Forwarded-For", NICE_HOST)
.get(); .get();
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null); final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
verifyNoInteractions(abusiveHostRules); verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN), eq(NICE_HOST));
verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN), eq(ABUSIVE_HOST));
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT); verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
} }
@Test @Test
void testSendAbusiveHostWithInvalidCaptcha() { void testSendWithInvalidCaptcha() {
Response response = Response response =
resources.getJerseyTest() resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER)) .target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("captcha", INVALID_CAPTCHA_TOKEN) .queryParam("captcha", INVALID_CAPTCHA_TOKEN)
.request() .request()
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST) .header("X-Forwarded-For", NICE_HOST)
.get(); .get();
assertThat(response.getStatus()).isEqualTo(402); assertThat(response.getStatus()).isEqualTo(402);
verifyNoInteractions(abusiveHostRules); verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN), eq(NICE_HOST));
verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN), eq(ABUSIVE_HOST));
verifyNoInteractions(registrationServiceClient); verifyNoInteractions(registrationServiceClient);
} }
@Test @Test
void testSendRateLimitedHostAutoBlock() { void testSendRateLimitedHost() {
Response response = Response response =
resources.getJerseyTest() resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER)) .target(String.format("/v1/accounts/sms/code/%s", SENDER))
@ -718,10 +681,6 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402); assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_IP_HOST));
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_IP_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoInteractions(recaptchaClient); verifyNoInteractions(recaptchaClient);
verifyNoInteractions(registrationServiceClient); verifyNoInteractions(registrationServiceClient);
} }
@ -739,55 +698,10 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402); assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_PREFIX_HOST));
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_PREFIX_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoInteractions(recaptchaClient); verifyNoInteractions(recaptchaClient);
verifyNoInteractions(registrationServiceClient); verifyNoInteractions(registrationServiceClient);
} }
@Test
void testSendRateLimitedHostNoAutoBlock() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header(HttpHeaders.X_FORWARDED_FOR, RATE_LIMITED_HOST2)
.get();
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_HOST2));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoInteractions(recaptchaClient);
verifyNoInteractions(registrationServiceClient);
}
@Test
void testSendMultipleHost() {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.request()
.header(HttpHeaders.X_FORWARDED_FOR, NICE_HOST + ", " + ABUSIVE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules, times(1)).isBlocked(eq(ABUSIVE_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoInteractions(registrationServiceClient);
}
@Test @Test
void testSendRestrictedHostOut() { void testSendRestrictedHostOut() {
@ -805,7 +719,6 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402); assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
verifyNoInteractions(registrationServiceClient); verifyNoInteractions(registrationServiceClient);
} }
@ -884,7 +797,7 @@ class AccountControllerTest {
resources.getJerseyTest() resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", TEST_NUMBER)) .target(String.format("/v1/accounts/sms/code/%s", TEST_NUMBER))
.request() .request()
.header(HttpHeaders.X_FORWARDED_FOR, ABUSIVE_HOST) .header("X-Forwarded-For", RATE_LIMITED_IP_HOST)
.get(); .get();
final ArgumentCaptor<StoredVerificationCode> captor = ArgumentCaptor.forClass(StoredVerificationCode.class); final ArgumentCaptor<StoredVerificationCode> captor = ArgumentCaptor.forClass(StoredVerificationCode.class);

View File

@ -1,88 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.tests.storage;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.time.Duration;
import java.time.Instant;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class AbusiveHostRulesTest {
@RegisterExtension
private static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private AbusiveHostRules abusiveHostRules;
private DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager;
@BeforeEach
void setup() throws JsonProcessingException {
@SuppressWarnings("unchecked")
DynamicConfigurationManager<DynamicConfiguration> m = mock(DynamicConfigurationManager.class);
this.mockDynamicConfigManager = m;
when(mockDynamicConfigManager.getConfiguration()).thenReturn(generateConfig(Duration.ofHours(1)));
this.abusiveHostRules = new AbusiveHostRules(REDIS_CLUSTER_EXTENSION.getRedisCluster(), mockDynamicConfigManager);
}
DynamicConfiguration generateConfig(Duration expireDuration) throws JsonProcessingException {
final String configString = String.format("""
captcha:
scoreFloor: 1.0
abusiveHostRules:
expirationTime: %s
""", expireDuration);
return DynamicConfigurationManager
.parseConfiguration(configString, DynamicConfiguration.class)
.orElseThrow();
}
@Test
void testBlockedHost() {
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
connection.sync().set(AbusiveHostRules.prefix("192.168.1.1"), "1"));
assertThat(abusiveHostRules.isBlocked("192.168.1.1")).isTrue();
}
@Test
void testUnblocked() {
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
connection.sync().set(AbusiveHostRules.prefix("192.168.1.1"), "1"));
assertThat(abusiveHostRules.isBlocked("172.17.1.1")).isFalse();
}
@Test
void testInsertBlocked() {
abusiveHostRules.setBlockedHost("172.17.0.1");
assertThat(abusiveHostRules.isBlocked("172.17.0.1")).isTrue();
abusiveHostRules.setBlockedHost("172.17.0.1");
assertThat(abusiveHostRules.isBlocked("172.17.0.1")).isTrue();
}
@Test
void testExpiration() throws Exception {
when(mockDynamicConfigManager.getConfiguration()).thenReturn(generateConfig(Duration.ofSeconds(1)));
abusiveHostRules.setBlockedHost("192.168.1.1");
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
while (true) {
if (!abusiveHostRules.isBlocked("192.168.1.1")) {
break;
}
}
});
}
}