Use redis for abusive hosts autoblock

Also delete postgres dependencies that we no longer need
This commit is contained in:
Ravi Khadiwala
2022-05-25 10:37:39 -05:00
committed by ravi-signal
parent 5df24edebf
commit 5cfb133f79
20 changed files with 135 additions and 671 deletions

View File

@@ -87,7 +87,6 @@ import org.whispersystems.textsecuregcm.push.GcmMessage;
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.sms.SmsSender;
import org.whispersystems.textsecuregcm.sms.TwilioVerifyExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRule;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
@@ -113,12 +112,13 @@ class AccountControllerTest {
private static final String SENDER_REG_LOCK = "+14158888888";
private static final String SENDER_HAS_STORAGE = "+14159999999";
private static final String SENDER_TRANSFER = "+14151111112";
private static final String RESTRICTED_COUNTRY = "800";
private static final String RESTRICTED_NUMBER = "+" + RESTRICTED_COUNTRY + "11111111";
private static final UUID SENDER_REG_LOCK_UUID = UUID.randomUUID();
private static final UUID SENDER_TRANSFER_UUID = UUID.randomUUID();
private static final String ABUSIVE_HOST = "192.168.1.1";
private static final String RESTRICTED_HOST = "192.168.1.2";
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_PREFIX_HOST = "10.0.0.2";
@@ -276,12 +276,12 @@ class AccountControllerTest {
.thenReturn(dynamicConfiguration);
DynamicCaptchaConfiguration signupCaptchaConfig = new DynamicCaptchaConfiguration();
signupCaptchaConfig.setSignupCountryCodes(Set.of(RESTRICTED_COUNTRY));
when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig);
}
when(abusiveHostRules.getAbusiveHostRulesFor(eq(ABUSIVE_HOST))).thenReturn(Collections.singletonList(new AbusiveHostRule(ABUSIVE_HOST, true, Collections.emptyList())));
when(abusiveHostRules.getAbusiveHostRulesFor(eq(RESTRICTED_HOST))).thenReturn(Collections.singletonList(new AbusiveHostRule(RESTRICTED_HOST, false, Collections.singletonList("+123"))));
when(abusiveHostRules.getAbusiveHostRulesFor(eq(NICE_HOST))).thenReturn(Collections.emptyList());
when(abusiveHostRules.isBlocked(eq(ABUSIVE_HOST))).thenReturn(true);
when(abusiveHostRules.isBlocked(eq(NICE_HOST))).thenReturn(false);
when(recaptchaClient.verify(eq(INVALID_CAPTCHA_TOKEN), anyString())).thenReturn(false);
when(recaptchaClient.verify(eq(VALID_CAPTCHA_TOKEN), anyString())).thenReturn(true);
@@ -496,7 +496,7 @@ class AccountControllerTest {
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
}
verifyNoMoreInteractions(smsSender);
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@Test
@@ -563,7 +563,7 @@ class AccountControllerTest {
} else {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Collections.emptyList()));
}
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@ParameterizedTest
@@ -595,7 +595,7 @@ class AccountControllerTest {
} else {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Locale.LanguageRange.parse("pt-BR")));
}
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@ParameterizedTest
@@ -628,7 +628,7 @@ class AccountControllerTest {
verify(smsSender).deliverVoxVerification(eq(SENDER), anyString(), eq(Locale.LanguageRange
.parse("en-US;q=1, ar-US;q=0.9, fa-US;q=0.8, zh-Hans-US;q=0.7, ru-RU;q=0.6, zh-Hant-US;q=0.5")));
}
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@Test
@@ -646,7 +646,7 @@ class AccountControllerTest {
verify(smsSender, never()).deliverVoxVerification(eq(SENDER), anyString(), any());
verify(smsSender, never()).deliverVoxVerificationWithTwilioVerify(eq(SENDER), anyString(), any());
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@ParameterizedTest
@@ -677,7 +677,7 @@ class AccountControllerTest {
} else {
verify(smsSender).deliverSmsVerification(eq(SENDER_PREAUTH), eq(Optional.empty()), anyString());
}
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
}
@Test
@@ -796,7 +796,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(ABUSIVE_HOST));
verify(abusiveHostRules).isBlocked(eq(ABUSIVE_HOST));
verifyNoMoreInteractions(smsSender);
}
@@ -880,8 +880,8 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RATE_LIMITED_IP_HOST));
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_IP_HOST), eq("Auto-Block"));
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_IP_HOST));
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_IP_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(recaptchaClient);
@@ -910,8 +910,8 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RATE_LIMITED_PREFIX_HOST));
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_PREFIX_HOST), eq("Auto-Block"));
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_PREFIX_HOST));
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_PREFIX_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(recaptchaClient);
@@ -940,7 +940,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RATE_LIMITED_HOST2));
verify(abusiveHostRules).isBlocked(eq(RATE_LIMITED_HOST2));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(recaptchaClient);
@@ -965,7 +965,7 @@ class AccountControllerTest {
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules, times(1)).getAbusiveHostRulesFor(eq(ABUSIVE_HOST));
verify(abusiveHostRules, times(1)).isBlocked(eq(ABUSIVE_HOST));
verifyNoMoreInteractions(abusiveHostRules);
verifyNoMoreInteractions(smsSender);
@@ -979,17 +979,20 @@ class AccountControllerTest {
when(verifyExperimentEnrollmentManager.isEnrolled(any(), anyString(), anyList(), anyString()))
.thenReturn(enrolledInVerifyExperiment);
final String challenge = "challenge";
when(pendingAccountsManager.getCodeForNumber(RESTRICTED_NUMBER)).thenReturn(Optional.of(new StoredVerificationCode("123456", System.currentTimeMillis(), challenge, null)));
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("challenge", "1234-push")
.target(String.format("/v1/accounts/sms/code/%s", RESTRICTED_NUMBER))
.queryParam("challenge", challenge)
.request()
.header("X-Forwarded-For", RESTRICTED_HOST)
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(402);
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RESTRICTED_HOST));
verify(abusiveHostRules).isBlocked(eq(NICE_HOST));
verifyNoMoreInteractions(smsSender);
}
@@ -1004,27 +1007,25 @@ class AccountControllerTest {
when(smsSender.deliverSmsVerificationWithTwilioVerify(anyString(), any(), anyString(), anyList()))
.thenReturn(CompletableFuture.completedFuture(Optional.of("VerificationSid")));
}
final String number = "+12345678901";
final String challenge = "challenge";
when(pendingAccountsManager.getCodeForNumber(number)).thenReturn(Optional.of(new StoredVerificationCode("123456", System.currentTimeMillis(), challenge, null)));
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of(new StoredVerificationCode("123456", System.currentTimeMillis(), challenge, null)));
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", number))
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("challenge", challenge)
.request()
.header("X-Forwarded-For", RESTRICTED_HOST)
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(200);
if (enrolledInVerifyExperiment) {
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(number), eq(Optional.empty()), anyString(),
verify(smsSender).deliverSmsVerificationWithTwilioVerify(eq(SENDER), eq(Optional.empty()), anyString(),
eq(Collections.emptyList()));
} else {
verify(smsSender).deliverSmsVerification(eq(number), eq(Optional.empty()), anyString());
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
}
verifyNoMoreInteractions(smsSender);
@@ -1035,7 +1036,7 @@ class AccountControllerTest {
void testVerifyCode(final boolean enrolledInVerifyExperiment) throws Exception {
if (enrolledInVerifyExperiment) {
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(
Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis(), "1234-push", "VerificationSid")));;
Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis(), "1234-push", "VerificationSid")));
}
resources.getJerseyTest()

View File

@@ -6,129 +6,83 @@
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 io.zonky.test.db.postgres.embedded.LiquibasePreparer;
import io.zonky.test.db.postgres.junit5.EmbeddedPostgresExtension;
import io.zonky.test.db.postgres.junit5.PreparedDbExtension;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.jdbi.v3.core.Jdbi;
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.CircuitBreakerConfiguration;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRule;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class AbusiveHostRulesTest {
@RegisterExtension
PreparedDbExtension db = EmbeddedPostgresExtension.preparedDatabase(
LiquibasePreparer.forClasspathLocation("abusedb.xml"));
@RegisterExtension
PreparedDbExtension newDb = EmbeddedPostgresExtension.preparedDatabase(
LiquibasePreparer.forClasspathLocation("abusedb.xml"));
private static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private AbusiveHostRules abusiveHostRules;
private DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager;
@BeforeEach
void setup() {
this.abusiveHostRules = new AbusiveHostRules(
new FaultTolerantDatabase("abusive_hosts-test", Jdbi.create(db.getTestDatabase()),
new CircuitBreakerConfiguration()));
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() throws SQLException {
PreparedStatement statement = db.getTestDatabase().getConnection()
.prepareStatement("INSERT INTO abusive_host_rules (host, blocked) VALUES (?::INET, ?)");
statement.setString(1, "192.168.1.1");
statement.setInt(2, 1);
statement.execute();
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("192.168.1.1");
assertThat(rules.size()).isEqualTo(1);
assertThat(rules.get(0).regions().isEmpty()).isTrue();
assertThat(rules.get(0).host()).isEqualTo("192.168.1.1");
assertThat(rules.get(0).blocked()).isTrue();
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 testBlockedCidr() throws SQLException {
PreparedStatement statement = db.getTestDatabase().getConnection()
.prepareStatement("INSERT INTO abusive_host_rules (host, blocked) VALUES (?::INET, ?)");
statement.setString(1, "192.168.1.0/24");
statement.setInt(2, 1);
statement.execute();
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("192.168.1.1");
assertThat(rules.size()).isEqualTo(1);
assertThat(rules.get(0).regions().isEmpty()).isTrue();
assertThat(rules.get(0).host()).isEqualTo("192.168.1.0/24");
assertThat(rules.get(0).blocked()).isTrue();
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 testUnblocked() throws SQLException {
PreparedStatement statement = db.getTestDatabase().getConnection()
.prepareStatement("INSERT INTO abusive_host_rules (host, blocked) VALUES (?::INET, ?)");
statement.setString(1, "192.168.1.0/24");
statement.setInt(2, 1);
statement.execute();
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("172.17.1.1");
assertThat(rules.isEmpty()).isTrue();
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 testRestricted() throws SQLException {
PreparedStatement statement = db.getTestDatabase().getConnection()
.prepareStatement("INSERT INTO abusive_host_rules (host, blocked, regions) VALUES (?::INET, ?, ?)");
statement.setString(1, "192.168.1.0/24");
statement.setInt(2, 0);
statement.setString(3, "+1,+49");
statement.execute();
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("192.168.1.100");
assertThat(rules.size()).isEqualTo(1);
assertThat(rules.get(0).blocked()).isFalse();
assertThat(rules.get(0).regions()).isEqualTo(Arrays.asList("+1", "+49"));
}
@Test
void testInsertBlocked() throws Exception {
abusiveHostRules.setBlockedHost("172.17.0.1", "Testing one two");
PreparedStatement statement = db.getTestDatabase().getConnection()
.prepareStatement("SELECT * from abusive_host_rules WHERE host = ?::inet");
statement.setString(1, "172.17.0.1");
ResultSet resultSet = statement.executeQuery();
assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getInt("blocked")).isEqualTo(1);
assertThat(resultSet.getString("regions")).isNullOrEmpty();
assertThat(resultSet.getString("notes")).isEqualTo("Testing one two");
abusiveHostRules.setBlockedHost("172.17.0.1", "Different notes");
statement = db.getTestDatabase().getConnection()
.prepareStatement("SELECT * from abusive_host_rules WHERE host = ?::inet");
statement.setString(1, "172.17.0.1");
resultSet = statement.executeQuery();
assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getInt("blocked")).isEqualTo(1);
assertThat(resultSet.getString("regions")).isNullOrEmpty();
assertThat(resultSet.getString("notes")).isEqualTo("Testing one two");
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;
}
}
});
}
}