From ad1aeea74b6877cc9fa6a996081daf2a3017733d Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Mon, 18 Oct 2021 12:04:19 -0400 Subject: [PATCH] Add an abusive message filter interface and submodule --- .gitmodules | 11 + abusive-message-filter | 1 + pom.xml | 23 ++ service/pom.xml | 208 ++++++++++-------- .../WhisperServerConfiguration.java | 9 + .../textsecuregcm/WhisperServerService.java | 30 +++ .../abuse/AbusiveMessageFilter.java | 33 +++ .../abuse/FilterAbusiveMessages.java | 21 ++ .../AbusiveMessageFilterConfiguration.java | 26 +++ .../controllers/MessageController.java | 3 + .../storage/DynamicConfigurationManager.java | 2 +- 11 files changed, 272 insertions(+), 95 deletions(-) create mode 100644 .gitmodules create mode 160000 abusive-message-filter create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/abuse/AbusiveMessageFilter.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/abuse/FilterAbusiveMessages.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/AbusiveMessageFilterConfiguration.java diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..f9d8a5ab7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,11 @@ +# Note that the implmentation of the abusive message filter is private; internal +# developers will need to override this URL with: +# +# ``` +# git config submodule.abusive-message-filter.url PRIVATE_URL +# ``` +# +# External developers may safely ignore this submodule. +[submodule "abusive-message-filter"] + path = abusive-message-filter + url = REDACTED diff --git a/abusive-message-filter b/abusive-message-filter new file mode 160000 index 000000000..817f2e867 --- /dev/null +++ b/abusive-message-filter @@ -0,0 +1 @@ +Subproject commit 817f2e867ec1160f793c95f16f50b75c35f2e887 diff --git a/pom.xml b/pom.xml index 1ddf8211a..5cdfcc2e6 100644 --- a/pom.xml +++ b/pom.xml @@ -298,6 +298,29 @@ + + + include-abusive-message-filter + + + abusive-message-filter/pom.xml + + + + abusive-message-filter + + + + + exclude-abusive-message-filter + + + abusive-message-filter/pom.xml + + + + + diff --git a/service/pom.xml b/service/pom.xml index 306da335d..57eb6c922 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -462,104 +462,112 @@ + + + exclude-abusive-message-filter + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + true + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + package + + shade + + + + + + org.whispersystems.textsecuregcm.WhisperServerService + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + assembly.xml + + + + + make-assembly + package + + single + + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + read-deploy-configuration + deploy + + read-project-properties + + + ${project.basedir}/config/deploy.properties + + + + + + + org.signal + s3-upload-maven-plugin + 1.6-SNAPSHOT + + ${project.build.directory}/${project.build.finalName}-bin.tar.gz + ${deploy.bucketName} + ${deploy.bucketRegion} + ${project.build.finalName}-bin.tar.gz + + + + deploy-to-s3 + deploy + + s3-upload + + + + + + + + ${project.parent.artifactId}-${project.version} - - org.apache.maven.plugins - maven-shade-plugin - 3.2.4 - - true - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - package - - shade - - - - - - org.whispersystems.textsecuregcm.WhisperServerService - - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.3.0 - - - assembly.xml - - - - - make-assembly - package - - single - - - - - - - org.codehaus.mojo - properties-maven-plugin - 1.0.0 - - - read-deploy-configuration - deploy - - read-project-properties - - - ${project.basedir}/config/deploy.properties - - - - - - - org.signal - s3-upload-maven-plugin - 1.6-SNAPSHOT - - ${project.build.directory}/${project.build.finalName}-bin.tar.gz - ${deploy.bucketName} - ${deploy.bucketRegion} - ${project.build.finalName}-bin.tar.gz - - - - deploy-to-s3 - deploy - - s3-upload - - - - - org.codehaus.mojo templating-maven-plugin @@ -573,6 +581,18 @@ + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index 400a2a06f..a7a3a3400 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import javax.validation.Valid; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.AbusiveMessageFilterConfiguration; import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration; import org.whispersystems.textsecuregcm.configuration.AccountsDatabaseConfiguration; import org.whispersystems.textsecuregcm.configuration.AccountsDynamoDbConfiguration; @@ -330,6 +331,10 @@ public class WhisperServerConfiguration extends Configuration { @JsonProperty private ReportMessageConfiguration reportMessage = new ReportMessageConfiguration(); + @Valid + @JsonProperty + private AbusiveMessageFilterConfiguration abusiveMessageFilter; + private Map transparentDataIndex = new HashMap<>(); public StripeConfiguration getStripe() { @@ -565,4 +570,8 @@ public class WhisperServerConfiguration extends Configuration { public ReportMessageConfiguration getReportMessageConfiguration() { return reportMessage; } + + public AbusiveMessageFilterConfiguration getAbusiveMessageFilterConfiguration() { + return abusiveMessageFilter; + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 25f31683f..e29785a3e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -44,6 +44,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Optional; +import java.util.ServiceLoader; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; @@ -52,6 +53,7 @@ import java.util.concurrent.TimeUnit; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletRegistration; +import javax.ws.rs.container.DynamicFeature; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.glassfish.jersey.server.ServerProperties; import org.jdbi.v3.core.Jdbi; @@ -64,6 +66,8 @@ import org.signal.zkgroup.receipts.ServerZkReceiptOperations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.dispatch.DispatchManager; +import org.whispersystems.textsecuregcm.abuse.AbusiveMessageFilter; +import org.whispersystems.textsecuregcm.abuse.FilterAbusiveMessages; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.CertificateGenerator; @@ -659,6 +663,32 @@ public class WhisperServerService extends Application provisioningEnvironment = new WebSocketEnvironment<>(environment, webSocketEnvironment.getRequestLog(), 60000); provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager)); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/abuse/AbusiveMessageFilter.java b/service/src/main/java/org/whispersystems/textsecuregcm/abuse/AbusiveMessageFilter.java new file mode 100644 index 000000000..0a0538769 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/abuse/AbusiveMessageFilter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.abuse; + +import io.dropwizard.lifecycle.Managed; +import javax.ws.rs.container.ContainerRequestFilter; +import java.io.IOException; + +/** + * An abusive message filter is a {@link ContainerRequestFilter} that filters requests to message-sending endpoints to + * detect and respond to patterns of abusive behavior. + *

+ * Abusive message filters are managed components that are generally loaded dynamically via a + * {@link java.util.ServiceLoader}. Their {@link #configure(String)} method will be called prior to be adding to the + * server's pool of {@link Managed} objects. + *

+ * Abusive message filters must be annotated with {@link FilterAbusiveMessages}, a name binding annotation that + * restricts the endpoints to which the filter may apply. + */ +public interface AbusiveMessageFilter extends ContainerRequestFilter, Managed { + + /** + * Configures this abusive message filter. This method will be called before the filter is added to the server's pool + * of managed objects and before the server processes any requests. + * + * @param environmentName the name of the environment in which this filter is running (e.g. "staging" or "production") + * @throws IOException if the filter could not read its configuration source for any reason + */ + void configure(String environmentName) throws IOException; +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/abuse/FilterAbusiveMessages.java b/service/src/main/java/org/whispersystems/textsecuregcm/abuse/FilterAbusiveMessages.java new file mode 100644 index 000000000..09c7f556f --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/abuse/FilterAbusiveMessages.java @@ -0,0 +1,21 @@ +/* + * Copyright 2013-2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.abuse; + +import javax.ws.rs.NameBinding; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A name-binding annotation that associates {@link AbusiveMessageFilter}s with resource methods. + */ +@NameBinding +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface FilterAbusiveMessages { +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AbusiveMessageFilterConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AbusiveMessageFilterConfiguration.java new file mode 100644 index 000000000..c815aca1b --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AbusiveMessageFilterConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright 2013-2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.configuration; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.validation.constraints.NotBlank; + +public class AbusiveMessageFilterConfiguration { + + @JsonProperty + @NotBlank + private final String environment; + + @JsonCreator + public AbusiveMessageFilterConfiguration(@JsonProperty("environment") final String environment) { + this.environment = environment; + } + + public String getEnvironment() { + return environment; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index df1762b12..2a032b371 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -58,6 +58,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.abuse.FilterAbusiveMessages; import org.whispersystems.textsecuregcm.auth.Anonymous; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.CombinedUnidentifiedSenderAccessKeys; @@ -163,6 +164,7 @@ public class MessageController { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) + @FilterAbusiveMessages public Response sendMessage(@Auth Optional source, @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, @HeaderParam("User-Agent") String userAgent, @@ -285,6 +287,7 @@ public class MessageController { @PUT @Consumes(MultiRecipientMessageProvider.MEDIA_TYPE) @Produces(MediaType.APPLICATION_JSON) + @FilterAbusiveMessages public Response sendMultiRecipientMessage( @HeaderParam(OptionalAccess.UNIDENTIFIED) CombinedUnidentifiedSenderAccessKeys accessKeys, @HeaderParam("User-Agent") String userAgent, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java index 4fb9217dd..cb22d92ee 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java @@ -59,7 +59,7 @@ public class DynamicConfigurationManager { } @VisibleForTesting - public DynamicConfigurationManager(AppConfigClient appConfigClient, String application, String environment, + DynamicConfigurationManager(AppConfigClient appConfigClient, String application, String environment, String configurationName, String clientId, Class configurationClass) { this.appConfigClient = appConfigClient; this.application = application;