0
0
mirror of https://github.com/signalapp/Signal-Server.git synced 2024-09-19 19:42:18 +02:00

Add tests for WhisperServerService#run

Additionally, `LocalWhisperServerService` may be used for integration testing.
This commit is contained in:
Chris Eager 2024-04-29 11:05:35 -05:00 committed by GitHub
parent b6f8bca361
commit 0e4be0c85a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
84 changed files with 2156 additions and 552 deletions

View File

@ -22,5 +22,8 @@ jobs:
# work around an issue with actions/runner setting an incorrect HOME in containers, which breaks maven caching # work around an issue with actions/runner setting an incorrect HOME in containers, which breaks maven caching
# https://github.com/actions/setup-java/issues/356 # https://github.com/actions/setup-java/issues/356
HOME: /root HOME: /root
- name: Install APT packages
# ca-certificates: required for AWS CRT client
run: apt update && apt install -y ca-certificates
- name: Build with Maven - name: Build with Maven
run: ./mvnw -e -B verify run: ./mvnw -e -B verify

30
TESTING.md Normal file
View File

@ -0,0 +1,30 @@
# Testing
## Automated tests
The full suite of automated tests can be run using Maven from the project root:
```sh
./mvnw verify
```
## Test server
The service can be run in a feature-limited test mode by running the Maven `integration-test`
goal with the `test-server` profile activated:
```sh
./mvnw integration-test -Ptest-server [-DskipTests=true]
```
This runs [`LocalWhisperServerService`][lwss] with [test configuration][test.yml] and [secrets][test secrets]. External
registration clients are stubbed so that:
- a captcha requirement can be satisfied with `test.test.registration.test`
- any string will be accepted for a phone verification code
[lwss]: service/src/test/java/org/whispersystems/textsecuregcm/LocalWhisperServerService.java
[test.yml]: service/src/test/resources/config/test.yml
[test secrets]: service/src/test/resources/config/test-secrets-bundle.yml

View File

@ -15,7 +15,6 @@ import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager; import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessions; import org.whispersystems.textsecuregcm.storage.VerificationSessions;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
@ -31,13 +30,9 @@ public class IntegrationTools {
public static IntegrationTools create(final Config config) { public static IntegrationTools create(final Config config) {
final AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build(); final AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build();
final DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( final DynamoDbAsyncClient dynamoDbAsyncClient = config.dynamoDbClient().buildAsyncClient(credentialsProvider);
config.dynamoDbClientConfiguration(),
credentialsProvider);
final DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( final DynamoDbClient dynamoDbClient = config.dynamoDbClient().buildSyncClient(credentialsProvider);
config.dynamoDbClientConfiguration(),
credentialsProvider);
final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient); config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient);

View File

@ -5,11 +5,11 @@
package org.signal.integration.config; package org.signal.integration.config;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration; import org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory;
public record Config(String domain, public record Config(String domain,
String rootCert, String rootCert,
DynamoDbClientConfiguration dynamoDbClientConfiguration, DynamoDbClientFactory dynamoDbClient,
DynamoDbTables dynamoDbTables, DynamoDbTables dynamoDbTables,
String prescribedRegistrationNumber, String prescribedRegistrationNumber,
String prescribedRegistrationCode) { String prescribedRegistrationCode) {

View File

@ -67,14 +67,15 @@ braintree:
supportedCurrenciesByPaymentMethod: supportedCurrenciesByPaymentMethod:
PAYPAL: PAYPAL:
- usd - usd
pubSubProject: example-project pubSubPublisher:
pubSubTopic: example-topic project: example-project
pubSubCredentialConfiguration: | topic: example-topic
{ credentialConfiguration: |
"credential": "configuration" {
} "credential": "configuration"
}
dynamoDbClientConfiguration: dynamoDbClient:
region: us-west-2 # AWS Region region: us-west-2 # AWS Region
dynamoDbTables: dynamoDbTables:
@ -138,8 +139,9 @@ cacheCluster: # Redis server configuration for cache cluster
clientPresenceCluster: # Redis server configuration for client presence cluster clientPresenceCluster: # Redis server configuration for client presence cluster
configurationUri: redis://redis.example.com:6379/ configurationUri: redis://redis.example.com:6379/
pubsub: # Redis server configuration for pubsub cluster provisioning:
uri: redis://redis.example.com:6379/ pubsub: # Redis server configuration for pubsub cluster
uri: redis://redis.example.com:6379/
pushSchedulerCluster: # Redis server configuration for push scheduler cluster pushSchedulerCluster: # Redis server configuration for push scheduler cluster
configurationUri: redis://redis.example.com:6379/ configurationUri: redis://redis.example.com:6379/
@ -218,9 +220,10 @@ metricsCluster:
configurationUri: redis://redis.example.com:6379/ configurationUri: redis://redis.example.com:6379/
awsAttachments: # AWS S3 configuration awsAttachments: # AWS S3 configuration
accessKey: secret://awsAttachments.accessKey
accessSecret: secret://awsAttachments.accessSecret
bucket: aws-attachments bucket: aws-attachments
credentials:
accessKeyId: secret://awsAttachments.accessKey
secretAccessKey: secret://awsAttachments.accessSecret
region: us-west-2 region: us-west-2
gcpAttachments: # GCP Storage configuration gcpAttachments: # GCP Storage configuration
@ -245,9 +248,10 @@ fcm: # FCM configuration
credentials: secret://fcm.credentials credentials: secret://fcm.credentials
cdn: cdn:
accessKey: secret://cdn.accessKey
accessSecret: secret://cdn.accessSecret
bucket: cdn # S3 Bucket name bucket: cdn # S3 Bucket name
credentials:
accessKeyId: secret://cdn.accessKey
secretAccessKey: secret://cdn.accessSecret
region: us-west-2 # AWS region region: us-west-2 # AWS region
clientCdn: clientCdn:
@ -345,13 +349,14 @@ remoteConfig:
paymentsService: paymentsService:
userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret
fixerApiKey: secret://paymentsService.fixerApiKey
coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
coinMarketCapCurrencyIds:
MOB: 7878
paymentCurrencies: paymentCurrencies:
# list of symbols for supported currencies # list of symbols for supported currencies
- MOB - MOB
externalClients:
fixerApiKey: secret://paymentsService.fixerApiKey
coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
coinMarketCapCurrencyIds:
MOB: 7878
artService: artService:
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret

View File

@ -640,6 +640,31 @@
</plugins> </plugins>
</build> </build>
</profile> </profile>
<profile>
<id>test-server</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>start-test-server</id>
<phase>integration-test</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.LocalWhisperServerService</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
<build> <build>
@ -692,15 +717,15 @@
<goals> <goals>
<goal>java</goal> <goal>java</goal>
</goals> </goals>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
<classpathScope>test</classpathScope>
<arguments>
<argument>${project.basedir}/config</argument>
</arguments>
</configuration>
</execution> </execution>
</executions> </executions>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
<classpathScope>test</classpathScope>
<arguments>
<argument>${project.basedir}/config</argument>
</arguments>
</configuration>
</plugin> </plugin>
<plugin> <plugin>

View File

@ -15,37 +15,40 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.attachments.TusConfiguration; import org.whispersystems.textsecuregcm.attachments.TusConfiguration;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration; import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration; import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration; import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration;
import org.whispersystems.textsecuregcm.configuration.Cdn3StorageManagerConfiguration; import org.whispersystems.textsecuregcm.configuration.Cdn3StorageManagerConfiguration;
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration; import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.ClientCdnConfiguration; import org.whispersystems.textsecuregcm.configuration.ClientCdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.ClientReleaseConfiguration; import org.whispersystems.textsecuregcm.configuration.ClientReleaseConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
import org.whispersystems.textsecuregcm.configuration.DefaultAwsCredentialsFactory;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration; import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
import org.whispersystems.textsecuregcm.configuration.DogstatsdConfiguration; import org.whispersystems.textsecuregcm.configuration.DogstatsdConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration; import org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory;
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables; import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
import org.whispersystems.textsecuregcm.configuration.ExternalRequestFilterConfiguration; import org.whispersystems.textsecuregcm.configuration.ExternalRequestFilterConfiguration;
import org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory;
import org.whispersystems.textsecuregcm.configuration.FcmConfiguration; import org.whispersystems.textsecuregcm.configuration.FcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration; import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.GenericZkConfig; import org.whispersystems.textsecuregcm.configuration.GenericZkConfig;
import org.whispersystems.textsecuregcm.configuration.HCaptchaConfiguration; import org.whispersystems.textsecuregcm.configuration.HCaptchaClientFactory;
import org.whispersystems.textsecuregcm.configuration.LinkDeviceSecretConfiguration; import org.whispersystems.textsecuregcm.configuration.LinkDeviceSecretConfiguration;
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration; import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageByteLimitCardinalityEstimatorConfiguration; import org.whispersystems.textsecuregcm.configuration.MessageByteLimitCardinalityEstimatorConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration; import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
import org.whispersystems.textsecuregcm.configuration.NoiseWebSocketTunnelConfiguration; import org.whispersystems.textsecuregcm.configuration.NoiseWebSocketTunnelConfiguration;
import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration; import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration; import org.whispersystems.textsecuregcm.configuration.ProvisioningConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration; import org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory;
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration; import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration; import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration;
import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration; import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery3Configuration; import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery3Configuration;
@ -69,6 +72,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty @JsonProperty
private TlsKeyStoreConfiguration tlsKeyStore; private TlsKeyStoreConfiguration tlsKeyStore;
@NotNull
@Valid
@JsonProperty
AwsCredentialsProviderFactory awsCredentialsProvider = new DefaultAwsCredentialsFactory();
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
@ -82,7 +90,7 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private DynamoDbClientConfiguration dynamoDbClientConfiguration; private DynamoDbClientFactory dynamoDbClient;
@NotNull @NotNull
@Valid @Valid
@ -117,22 +125,22 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private DogstatsdConfiguration dogstatsd = new DogstatsdConfiguration(); private DatadogConfiguration dogstatsd = new DogstatsdConfiguration();
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private RedisClusterConfiguration cacheCluster; private FaultTolerantRedisClusterFactory cacheCluster;
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private RedisConfiguration pubsub; private FaultTolerantRedisClusterFactory metricsCluster;
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private RedisClusterConfiguration metricsCluster; private ProvisioningConfiguration provisioning;
@NotNull @NotNull
@Valid @Valid
@ -151,12 +159,12 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private RedisClusterConfiguration pushSchedulerCluster; private FaultTolerantRedisClusterFactory pushSchedulerCluster;
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private RedisClusterConfiguration rateLimitersCluster; private FaultTolerantRedisClusterFactory rateLimitersCluster;
@NotNull @NotNull
@Valid @Valid
@ -166,7 +174,7 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
private RedisClusterConfiguration clientPresenceCluster; private FaultTolerantRedisClusterFactory clientPresenceCluster;
@Valid @Valid
@NotNull @NotNull
@ -201,7 +209,7 @@ public class WhisperServerConfiguration extends Configuration {
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private HCaptchaConfiguration hCaptcha; private HCaptchaClientFactory hCaptcha;
@Valid @Valid
@NotNull @NotNull
@ -246,7 +254,7 @@ public class WhisperServerConfiguration extends Configuration {
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private AppConfigConfiguration appConfig; private DynamicConfigurationManagerFactory appConfig;
@Valid @Valid
@NotNull @NotNull
@ -270,12 +278,12 @@ public class WhisperServerConfiguration extends Configuration {
@Valid @Valid
@JsonProperty @JsonProperty
private SpamFilterConfiguration spamFilterConfiguration; private SpamFilterConfiguration spamFilter;
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private RegistrationServiceConfiguration registrationService; private RegistrationServiceClientFactory registrationService;
@Valid @Valid
@NotNull @NotNull
@ -305,28 +313,28 @@ public class WhisperServerConfiguration extends Configuration {
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private VirtualThreadConfiguration virtualThreadConfiguration = new VirtualThreadConfiguration(Duration.ofMillis(1)); private VirtualThreadConfiguration virtualThread = new VirtualThreadConfiguration(Duration.ofMillis(1));
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private MonitoredS3ObjectConfiguration maxmindCityDatabase; private S3ObjectMonitorFactory maxmindCityDatabase;
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private MonitoredS3ObjectConfiguration callingTurnDnsRecords; private S3ObjectMonitorFactory callingTurnDnsRecords;
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private MonitoredS3ObjectConfiguration callingTurnPerformanceTable; private S3ObjectMonitorFactory callingTurnPerformanceTable;
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty
private MonitoredS3ObjectConfiguration callingTurnManualTable; private S3ObjectMonitorFactory callingTurnManualTable;
@Valid @Valid
@NotNull @NotNull
@ -342,6 +350,10 @@ public class WhisperServerConfiguration extends Configuration {
return tlsKeyStore; return tlsKeyStore;
} }
public AwsCredentialsProviderFactory getAwsCredentialsConfiguration() {
return awsCredentialsProvider;
}
public StripeConfiguration getStripe() { public StripeConfiguration getStripe() {
return stripe; return stripe;
} }
@ -350,15 +362,15 @@ public class WhisperServerConfiguration extends Configuration {
return braintree; return braintree;
} }
public DynamoDbClientConfiguration getDynamoDbClientConfiguration() { public DynamoDbClientFactory getDynamoDbClientConfiguration() {
return dynamoDbClientConfiguration; return dynamoDbClient;
} }
public DynamoDbTables getDynamoDbTables() { public DynamoDbTables getDynamoDbTables() {
return dynamoDbTables; return dynamoDbTables;
} }
public HCaptchaConfiguration getHCaptchaConfiguration() { public HCaptchaClientFactory getHCaptchaConfiguration() {
return hCaptcha; return hCaptcha;
} }
@ -378,15 +390,15 @@ public class WhisperServerConfiguration extends Configuration {
return gcpAttachments; return gcpAttachments;
} }
public RedisClusterConfiguration getCacheClusterConfiguration() { public FaultTolerantRedisClusterFactory getCacheClusterConfiguration() {
return cacheCluster; return cacheCluster;
} }
public RedisConfiguration getPubsubCacheConfiguration() { public ProvisioningConfiguration getProvisioningConfiguration() {
return pubsub; return provisioning;
} }
public RedisClusterConfiguration getMetricsClusterConfiguration() { public FaultTolerantRedisClusterFactory getMetricsClusterConfiguration() {
return metricsCluster; return metricsCluster;
} }
@ -410,15 +422,15 @@ public class WhisperServerConfiguration extends Configuration {
return messageCache; return messageCache;
} }
public RedisClusterConfiguration getClientPresenceClusterConfiguration() { public FaultTolerantRedisClusterFactory getClientPresenceClusterConfiguration() {
return clientPresenceCluster; return clientPresenceCluster;
} }
public RedisClusterConfiguration getPushSchedulerCluster() { public FaultTolerantRedisClusterFactory getPushSchedulerCluster() {
return pushSchedulerCluster; return pushSchedulerCluster;
} }
public RedisClusterConfiguration getRateLimitersCluster() { public FaultTolerantRedisClusterFactory getRateLimitersCluster() {
return rateLimitersCluster; return rateLimitersCluster;
} }
@ -446,7 +458,7 @@ public class WhisperServerConfiguration extends Configuration {
return cdn3StorageManager; return cdn3StorageManager;
} }
public DogstatsdConfiguration getDatadogConfiguration() { public DatadogConfiguration getDatadogConfiguration() {
return dogstatsd; return dogstatsd;
} }
@ -489,7 +501,7 @@ public class WhisperServerConfiguration extends Configuration {
return remoteConfig; return remoteConfig;
} }
public AppConfigConfiguration getAppConfig() { public DynamicConfigurationManagerFactory getAppConfig() {
return appConfig; return appConfig;
} }
@ -510,10 +522,10 @@ public class WhisperServerConfiguration extends Configuration {
} }
public SpamFilterConfiguration getSpamFilterConfiguration() { public SpamFilterConfiguration getSpamFilterConfiguration() {
return spamFilterConfiguration; return spamFilter;
} }
public RegistrationServiceConfiguration getRegistrationServiceConfiguration() { public RegistrationServiceClientFactory getRegistrationServiceConfiguration() {
return registrationService; return registrationService;
} }
@ -538,22 +550,22 @@ public class WhisperServerConfiguration extends Configuration {
} }
public VirtualThreadConfiguration getVirtualThreadConfiguration() { public VirtualThreadConfiguration getVirtualThreadConfiguration() {
return virtualThreadConfiguration; return virtualThread;
} }
public MonitoredS3ObjectConfiguration getMaxmindCityDatabase() { public S3ObjectMonitorFactory getMaxmindCityDatabase() {
return maxmindCityDatabase; return maxmindCityDatabase;
} }
public MonitoredS3ObjectConfiguration getCallingTurnDnsRecords() { public S3ObjectMonitorFactory getCallingTurnDnsRecords() {
return callingTurnDnsRecords; return callingTurnDnsRecords;
} }
public MonitoredS3ObjectConfiguration getCallingTurnPerformanceTable() { public S3ObjectMonitorFactory getCallingTurnPerformanceTable() {
return callingTurnPerformanceTable; return callingTurnPerformanceTable;
} }
public MonitoredS3ObjectConfiguration getCallingTurnManualTable() { public S3ObjectMonitorFactory getCallingTurnManualTable() {
return callingTurnManualTable; return callingTurnManualTable;
} }

View File

@ -7,14 +7,7 @@ package org.whispersystems.textsecuregcm;
import static com.codahale.metrics.MetricRegistry.name; import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import com.google.api.gax.batching.BatchingSettings;
import com.google.api.gax.batching.FlowControlSettings;
import com.google.api.gax.batching.FlowController;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.cloud.pubsub.v1.Publisher;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.pubsub.v1.TopicName;
import io.dropwizard.auth.AuthDynamicFeature; import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.AuthFilter; import io.dropwizard.auth.AuthFilter;
import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.auth.AuthValueFactoryProvider;
@ -27,7 +20,6 @@ import io.dropwizard.core.server.DefaultServerFactory;
import io.dropwizard.core.setup.Bootstrap; import io.dropwizard.core.setup.Bootstrap;
import io.dropwizard.core.setup.Environment; import io.dropwizard.core.setup.Environment;
import io.dropwizard.jetty.HttpsConnectorFactory; import io.dropwizard.jetty.HttpsConnectorFactory;
import io.dropwizard.lifecycle.Managed;
import io.grpc.ServerBuilder; import io.grpc.ServerBuilder;
import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder; import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder;
import io.lettuce.core.metrics.MicrometerOptions; import io.lettuce.core.metrics.MicrometerOptions;
@ -36,9 +28,7 @@ import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.grpc.MetricCollectingServerInterceptor; import io.micrometer.core.instrument.binder.grpc.MetricCollectingServerInterceptor;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalAddress;
import java.io.ByteArrayInputStream;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -232,7 +222,6 @@ import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator;
import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager; import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
import org.whispersystems.textsecuregcm.subscriptions.StripeManager; import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
import org.whispersystems.textsecuregcm.util.BufferingInterceptor; import org.whispersystems.textsecuregcm.util.BufferingInterceptor;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import org.whispersystems.textsecuregcm.util.ManagedAwsCrt; import org.whispersystems.textsecuregcm.util.ManagedAwsCrt;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier; import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier;
@ -263,9 +252,7 @@ import org.whispersystems.websocket.WebSocketResourceProviderFactory;
import org.whispersystems.websocket.setup.WebSocketEnvironment; import org.whispersystems.websocket.setup.WebSocketEnvironment;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider;
import software.amazon.awssdk.http.crt.AwsCrtHttpClient; import software.amazon.awssdk.http.crt.AwsCrtHttpClient;
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
@ -279,9 +266,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
public static final String SECRETS_BUNDLE_FILE_NAME_PROPERTY = "secrets.bundle.filename"; public static final String SECRETS_BUNDLE_FILE_NAME_PROPERTY = "secrets.bundle.filename";
public static final software.amazon.awssdk.auth.credentials.AwsCredentialsProvider AWSSDK_CREDENTIALS_PROVIDER =
WebIdentityTokenFileCredentialsProvider.create();
@Override @Override
public void initialize(final Bootstrap<WhisperServerConfiguration> bootstrap) { public void initialize(final Bootstrap<WhisperServerConfiguration> bootstrap) {
// `SecretStore` needs to be initialized before Dropwizard reads the main application config file. // `SecretStore` needs to be initialized before Dropwizard reads the main application config file.
@ -328,16 +312,15 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
final Clock clock = Clock.systemUTC(); final Clock clock = Clock.systemUTC();
final int availableProcessors = Runtime.getRuntime().availableProcessors(); final int availableProcessors = Runtime.getRuntime().availableProcessors();
final AwsCredentialsProvider awsCredentialsProvider = config.getAwsCredentialsConfiguration().build();
UncaughtExceptionHandler.register(); UncaughtExceptionHandler.register();
ScheduledExecutorService dynamicConfigurationExecutor = environment.lifecycle() ScheduledExecutorService dynamicConfigurationExecutor = environment.lifecycle()
.scheduledExecutorService(name(getClass(), "dynamicConfiguration-%d")).threads(1).build(); .scheduledExecutorService(name(getClass(), "dynamicConfiguration-%d")).threads(1).build();
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = config.getAppConfig()
new DynamicConfigurationManager<>(config.getAppConfig().getApplication(), .build(DynamicConfiguration.class, dynamicConfigurationExecutor, awsCredentialsProvider);
config.getAppConfig().getEnvironment(),
config.getAppConfig().getConfigurationName(),
DynamicConfiguration.class, dynamicConfigurationExecutor);
dynamicConfigurationManager.start(); dynamicConfigurationManager.start();
MetricsUtil.configureRegistries(config, environment, dynamicConfigurationManager); MetricsUtil.configureRegistries(config, environment, dynamicConfigurationManager);
@ -362,11 +345,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
BankMandateTranslator bankMandateTranslator = new BankMandateTranslator(headerControlledResourceBundleLookup); BankMandateTranslator bankMandateTranslator = new BankMandateTranslator(headerControlledResourceBundleLookup);
environment.lifecycle().manage(new ManagedAwsCrt()); environment.lifecycle().manage(new ManagedAwsCrt());
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(config.getDynamoDbClientConfiguration(), DynamoDbAsyncClient dynamoDbAsyncClient = config.getDynamoDbClientConfiguration()
AWSSDK_CREDENTIALS_PROVIDER); .buildAsyncClient(awsCredentialsProvider);
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(config.getDynamoDbClientConfiguration(), DynamoDbClient dynamoDbClient = config.getDynamoDbClientConfiguration().buildSyncClient(awsCredentialsProvider);
AWSSDK_CREDENTIALS_PROVIDER);
BlockingQueue<Runnable> messageDeletionQueue = new LinkedBlockingQueue<>(); BlockingQueue<Runnable> messageDeletionQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(), Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(),
@ -428,18 +410,19 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
.build(); .build();
ConnectionEventLogger.logConnectionEvents(sharedClientResources); ConnectionEventLogger.logConnectionEvents(sharedClientResources);
FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache", FaultTolerantRedisCluster cacheCluster = config.getCacheClusterConfiguration()
config.getCacheClusterConfiguration(), sharedClientResources.mutate()); .build("main_cache", sharedClientResources.mutate());
FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages", FaultTolerantRedisCluster messagesCluster =
config.getMessageCacheConfiguration().getRedisClusterConfiguration(), sharedClientResources.mutate()); config.getMessageCacheConfiguration().getRedisClusterConfiguration()
FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence", .build("messages", sharedClientResources.mutate());
config.getClientPresenceClusterConfiguration(), sharedClientResources.mutate()); FaultTolerantRedisCluster clientPresenceCluster = config.getClientPresenceClusterConfiguration()
FaultTolerantRedisCluster metricsCluster = new FaultTolerantRedisCluster("metrics", .build("client_presence", sharedClientResources.mutate());
config.getMetricsClusterConfiguration(), sharedClientResources.mutate()); FaultTolerantRedisCluster metricsCluster = config.getMetricsClusterConfiguration()
FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", .build("metrics", sharedClientResources.mutate());
config.getPushSchedulerCluster(), sharedClientResources.mutate()); FaultTolerantRedisCluster pushSchedulerCluster = config.getPushSchedulerCluster().build("push_scheduler",
FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", sharedClientResources.mutate());
config.getRateLimitersCluster(), sharedClientResources.mutate()); FaultTolerantRedisCluster rateLimitersCluster = config.getRateLimitersCluster().build("rate_limiters",
sharedClientResources.mutate());
final BlockingQueue<Runnable> keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(100_000); final BlockingQueue<Runnable> keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(100_000);
Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(), Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(),
@ -551,14 +534,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
registrationRecoveryPasswords); registrationRecoveryPasswords);
UsernameHashZkProofVerifier usernameHashZkProofVerifier = new UsernameHashZkProofVerifier(); UsernameHashZkProofVerifier usernameHashZkProofVerifier = new UsernameHashZkProofVerifier();
RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient( RegistrationServiceClient registrationServiceClient = config.getRegistrationServiceConfiguration()
config.getRegistrationServiceConfiguration().host(), .build(environment, registrationCallbackExecutor, registrationIdentityTokenRefreshExecutor);
config.getRegistrationServiceConfiguration().port(),
config.getRegistrationServiceConfiguration().credentialConfigurationJson(),
config.getRegistrationServiceConfiguration().identityTokenAudience(),
config.getRegistrationServiceConfiguration().registrationCaCertificate(),
registrationCallbackExecutor,
registrationIdentityTokenRefreshExecutor);
SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client(svr2CredentialsGenerator, SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client(svr2CredentialsGenerator,
secureValueRecoveryServiceExecutor, secureValueRecoveryServiceRetryExecutor, config.getSvr2Configuration()); secureValueRecoveryServiceExecutor, secureValueRecoveryServiceRetryExecutor, config.getSvr2Configuration());
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator,
@ -595,9 +572,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
apnPushNotificationScheduler, pushLatencyManager); apnPushNotificationScheduler, pushLatencyManager);
RateLimiters rateLimiters = RateLimiters.createAndValidate(config.getLimitsConfiguration(), RateLimiters rateLimiters = RateLimiters.createAndValidate(config.getLimitsConfiguration(),
dynamicConfigurationManager, rateLimitersCluster); dynamicConfigurationManager, rateLimitersCluster);
ProvisioningManager provisioningManager = new ProvisioningManager(config.getPubsubCacheConfiguration().getUri(), ProvisioningManager provisioningManager = new ProvisioningManager(
sharedClientResources, config.getPubsubCacheConfiguration().getTimeout(), config.getProvisioningConfiguration().pubsub().build(sharedClientResources),
config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration()); config.getProvisioningConfiguration().circuitBreaker());
IssuedReceiptsManager issuedReceiptsManager = new IssuedReceiptsManager( IssuedReceiptsManager issuedReceiptsManager = new IssuedReceiptsManager(
config.getDynamoDbTables().getIssuedReceipts().getTableName(), config.getDynamoDbTables().getIssuedReceipts().getTableName(),
config.getDynamoDbTables().getIssuedReceipts().getExpiration(), config.getDynamoDbTables().getIssuedReceipts().getExpiration(),
@ -635,12 +612,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
"message_byte_limit", "message_byte_limit",
config.getMessageByteLimitCardinalityEstimator().period()); config.getMessageByteLimitCardinalityEstimator().period());
HCaptchaClient hCaptchaClient = new HCaptchaClient( HCaptchaClient hCaptchaClient = config.getHCaptchaConfiguration()
config.getHCaptchaConfiguration().getApiKey().value(), .build(hcaptchaRetryExecutor, dynamicConfigurationManager);
hcaptchaRetryExecutor,
config.getHCaptchaConfiguration().getCircuitBreaker(),
config.getHCaptchaConfiguration().getRetry(),
dynamicConfigurationManager);
HttpClient shortCodeRetrieverHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2) HttpClient shortCodeRetrieverHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10)).build(); .connectTimeout(Duration.ofSeconds(10)).build();
ShortCodeExpander shortCodeRetriever = new ShortCodeExpander(shortCodeRetrieverHttpClient, config.getShortCodeRetrieverConfiguration().baseUrl()); ShortCodeExpander shortCodeRetriever = new ShortCodeExpander(shortCodeRetrieverHttpClient, config.getShortCodeRetrieverConfiguration().baseUrl());
@ -652,8 +625,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ChangeNumberManager changeNumberManager = new ChangeNumberManager(messageSender, accountsManager); ChangeNumberManager changeNumberManager = new ChangeNumberManager(messageSender, accountsManager);
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build(); HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().fixerApiKey().value()); FixerClient fixerClient = config.getPaymentsServiceConfiguration().externalClients()
CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().coinMarketCapApiKey().value(), config.getPaymentsServiceConfiguration().coinMarketCapCurrencyIds()); .buildFixerClient(currencyClient);
CoinMarketCapClient coinMarketCapClient = config.getPaymentsServiceConfiguration().externalClients()
.buildCoinMarketCapClient(currencyClient);
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient,
cacheCluster, config.getPaymentsServiceConfiguration().paymentCurrencies(), recurringJobExecutor, Clock.systemUTC()); cacheCluster, config.getPaymentsServiceConfiguration().paymentCurrencies(), recurringJobExecutor, Clock.systemUTC());
VirtualThreadPinEventMonitor virtualThreadPinEventMonitor = new VirtualThreadPinEventMonitor( VirtualThreadPinEventMonitor virtualThreadPinEventMonitor = new VirtualThreadPinEventMonitor(
@ -661,39 +636,14 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
() -> dynamicConfigurationManager.getConfiguration().getVirtualThreads().allowedPinEvents(), () -> dynamicConfigurationManager.getConfiguration().getVirtualThreads().allowedPinEvents(),
config.getVirtualThreadConfiguration().pinEventThreshold()); config.getVirtualThreadConfiguration().pinEventThreshold());
final Publisher pubSubPublisher;
{
final FlowControlSettings flowControlSettings = FlowControlSettings.newBuilder()
.setLimitExceededBehavior(FlowController.LimitExceededBehavior.ThrowException)
.setMaxOutstandingElementCount(100L)
.setMaxOutstandingRequestBytes(16 * 1024 * 1024L) // 16MB
.build();
final BatchingSettings batchingSettings = BatchingSettings.newBuilder()
.setFlowControlSettings(flowControlSettings)
.setDelayThreshold(org.threeten.bp.Duration.ofMillis(10))
// These thresholds are actually the default, setting them explicitly since creating a custom batchingSettings resets them
.setElementCountThreshold(100L)
.setRequestByteThreshold(5000L)
.build();
try (final ByteArrayInputStream credentialConfigInputStream =
new ByteArrayInputStream(config.getBraintree().pubSubCredentialConfiguration().getBytes(StandardCharsets.UTF_8))) {
pubSubPublisher = Publisher.newBuilder(TopicName.of(config.getBraintree().pubSubProject(), config.getBraintree().pubSubTopic()))
.setCredentialsProvider(FixedCredentialsProvider.create(ExternalAccountCredentials.fromStream(credentialConfigInputStream)))
.setBatchingSettings(batchingSettings)
.build();
}
}
StripeManager stripeManager = new StripeManager(config.getStripe().apiKey().value(), subscriptionProcessorExecutor, StripeManager stripeManager = new StripeManager(config.getStripe().apiKey().value(), subscriptionProcessorExecutor,
config.getStripe().idempotencyKeyGenerator().value(), config.getStripe().boostDescription(), config.getStripe().supportedCurrenciesByPaymentMethod()); config.getStripe().idempotencyKeyGenerator().value(), config.getStripe().boostDescription(), config.getStripe().supportedCurrenciesByPaymentMethod());
BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(), BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(),
config.getBraintree().publicKey(), config.getBraintree().privateKey().value(), config.getBraintree().publicKey(), config.getBraintree().privateKey().value(),
config.getBraintree().environment(), config.getBraintree().environment(),
config.getBraintree().supportedCurrenciesByPaymentMethod(), config.getBraintree().merchantAccounts(), config.getBraintree().supportedCurrenciesByPaymentMethod(), config.getBraintree().merchantAccounts(),
config.getBraintree().graphqlUrl(), currencyManager, pubSubPublisher, config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor, config.getBraintree().graphqlUrl(), currencyManager, config.getBraintree().pubSubPublisher().build(),
config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor,
subscriptionProcessorRetryExecutor); subscriptionProcessorRetryExecutor);
environment.lifecycle().manage(apnSender); environment.lifecycle().manage(apnSender);
@ -708,10 +658,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(captchaChecker); final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(captchaChecker);
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider AwsCredentialsProvider cdnCredentialsProvider = config.getCdnConfiguration().credentials().build();
.create(AwsBasicCredentials.create(
config.getCdnConfiguration().accessKey().value(),
config.getCdnConfiguration().accessSecret().value()));
S3Client cdnS3Client = S3Client.builder() S3Client cdnS3Client = S3Client.builder()
.credentialsProvider(cdnCredentialsProvider) .credentialsProvider(cdnCredentialsProvider)
.region(Region.of(config.getCdnConfiguration().region())) .region(Region.of(config.getCdnConfiguration().region()))
@ -730,8 +677,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getGcpAttachmentsConfiguration().rsaSigningKey().value()); config.getGcpAttachmentsConfiguration().rsaSigningKey().value());
PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().region(), PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().region(),
config.getCdnConfiguration().bucket(), config.getCdnConfiguration().accessKey().value()); config.getCdnConfiguration().bucket(), config.getCdnConfiguration().credentials().accessKeyId().value());
PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().accessSecret().value(), PolicySigner profileCdnPolicySigner = new PolicySigner(
config.getCdnConfiguration().credentials().secretAccessKey().value(),
config.getCdnConfiguration().region()); config.getCdnConfiguration().region());
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().serverSecret().value()); ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().serverSecret().value());
@ -768,23 +716,27 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
MaxMindDatabaseManager geoIpCityDatabaseManager = new MaxMindDatabaseManager( MaxMindDatabaseManager geoIpCityDatabaseManager = new MaxMindDatabaseManager(
recurringConfigSyncExecutor, recurringConfigSyncExecutor,
awsCredentialsProvider,
config.getMaxmindCityDatabase(), config.getMaxmindCityDatabase(),
"city" "city"
); );
environment.lifecycle().manage(geoIpCityDatabaseManager); environment.lifecycle().manage(geoIpCityDatabaseManager);
CallDnsRecordsManager callDnsRecordsManager = new CallDnsRecordsManager( CallDnsRecordsManager callDnsRecordsManager = new CallDnsRecordsManager(
recurringConfigSyncExecutor, recurringConfigSyncExecutor,
awsCredentialsProvider,
config.getCallingTurnDnsRecords() config.getCallingTurnDnsRecords()
); );
environment.lifecycle().manage(callDnsRecordsManager); environment.lifecycle().manage(callDnsRecordsManager);
CallRoutingTableManager callRoutingTableManager = new CallRoutingTableManager( CallRoutingTableManager callRoutingTableManager = new CallRoutingTableManager(
recurringConfigSyncExecutor, recurringConfigSyncExecutor,
awsCredentialsProvider,
config.getCallingTurnPerformanceTable(), config.getCallingTurnPerformanceTable(),
"Performance" "Performance"
); );
environment.lifecycle().manage(callRoutingTableManager); environment.lifecycle().manage(callRoutingTableManager);
CallRoutingTableManager manualCallRoutingTableManager = new CallRoutingTableManager( CallRoutingTableManager manualCallRoutingTableManager = new CallRoutingTableManager(
recurringConfigSyncExecutor, recurringConfigSyncExecutor,
awsCredentialsProvider,
config.getCallingTurnManualTable(), config.getCallingTurnManualTable(),
"Manual" "Manual"
); );
@ -978,8 +930,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager, new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager,
registrationLockVerificationManager, rateLimiters), registrationLockVerificationManager, rateLimiters),
new ArtController(rateLimiters, artCredentialsGenerator), new ArtController(rateLimiters, artCredentialsGenerator),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().accessKey().value(), new AttachmentControllerV2(rateLimiters,
config.getAwsAttachmentsConfiguration().accessSecret().value(), config.getAwsAttachmentsConfiguration().credentials().accessKeyId().value(),
config.getAwsAttachmentsConfiguration().credentials().secretAccessKey().value(),
config.getAwsAttachmentsConfiguration().region(), config.getAwsAttachmentsConfiguration().bucket()), config.getAwsAttachmentsConfiguration().region(), config.getAwsAttachmentsConfiguration().bucket()),
new AttachmentControllerV3(rateLimiters, gcsAttachmentGenerator), new AttachmentControllerV3(rateLimiters, gcsAttachmentGenerator),
new AttachmentControllerV4(rateLimiters, gcsAttachmentGenerator, tusAttachmentGenerator, new AttachmentControllerV4(rateLimiters, gcsAttachmentGenerator, tusAttachmentGenerator,
@ -1012,8 +965,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new SecureStorageController(storageCredentialsGenerator), new SecureStorageController(storageCredentialsGenerator),
new SecureValueRecovery2Controller(svr2CredentialsGenerator, accountsManager), new SecureValueRecovery2Controller(svr2CredentialsGenerator, accountsManager),
new SecureValueRecovery3Controller(svr3CredentialsGenerator, accountsManager), new SecureValueRecovery3Controller(svr3CredentialsGenerator, accountsManager),
new StickerController(rateLimiters, config.getCdnConfiguration().accessKey().value(), new StickerController(rateLimiters, config.getCdnConfiguration().credentials().accessKeyId().value(),
config.getCdnConfiguration().accessSecret().value(), config.getCdnConfiguration().region(), config.getCdnConfiguration().credentials().secretAccessKey().value(), config.getCdnConfiguration().region(),
config.getCdnConfiguration().bucket()), config.getCdnConfiguration().bucket()),
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions), new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters,

View File

@ -11,18 +11,18 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
public class CallDnsRecordsManager implements Supplier<CallDnsRecords>, Managed { public class CallDnsRecordsManager implements Supplier<CallDnsRecords>, Managed {
@ -38,20 +38,10 @@ public class CallDnsRecordsManager implements Supplier<CallDnsRecords>, Managed
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
.build(); .build();
public CallDnsRecordsManager( public CallDnsRecordsManager(final ScheduledExecutorService executorService,
@Nonnull final ScheduledExecutorService executorService, final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration) {
@Nonnull final MonitoredS3ObjectConfiguration configuration
){
this.objectMonitor = new S3ObjectMonitor(
configuration.s3Region(),
configuration.s3Bucket(),
configuration.objectKey(),
configuration.maxSize(),
executorService,
configuration.refreshInterval(),
this::handleDatabaseChanged
);
this.objectMonitor = configuration.build(awsCredentialsProvider, executorService);
this.callDnsRecords.set(CallDnsRecords.empty()); this.callDnsRecords.set(CallDnsRecords.empty());
this.refreshTimer = Metrics.timer(MetricsUtil.name(CallDnsRecordsManager.class, "refresh")); this.refreshTimer = Metrics.timer(MetricsUtil.name(CallDnsRecordsManager.class, "refresh"));
} }
@ -74,14 +64,12 @@ public class CallDnsRecordsManager implements Supplier<CallDnsRecords>, Managed
@Override @Override
public void start() throws Exception { public void start() throws Exception {
Managed.super.start(); objectMonitor.start(this::handleDatabaseChanged);
objectMonitor.start();
} }
@Override @Override
public void stop() throws Exception { public void stop() throws Exception {
objectMonitor.stop(); objectMonitor.stop();
Managed.super.stop();
callDnsRecords.getAndSet(null); callDnsRecords.getAndSet(null);
} }

View File

@ -8,18 +8,18 @@ package org.whispersystems.textsecuregcm.calls.routing;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
public class CallRoutingTableManager implements Supplier<CallRoutingTable>, Managed { public class CallRoutingTableManager implements Supplier<CallRoutingTable>, Managed {
@ -33,21 +33,11 @@ public class CallRoutingTableManager implements Supplier<CallRoutingTable>, Mana
private static final Logger log = LoggerFactory.getLogger(CallRoutingTableManager.class); private static final Logger log = LoggerFactory.getLogger(CallRoutingTableManager.class);
public CallRoutingTableManager( public CallRoutingTableManager(final ScheduledExecutorService executorService,
@Nonnull final ScheduledExecutorService executorService, final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration,
@Nonnull final MonitoredS3ObjectConfiguration configuration, final String tableTag) {
@Nonnull final String tableTag
){
this.objectMonitor = new S3ObjectMonitor(
configuration.s3Region(),
configuration.s3Bucket(),
configuration.objectKey(),
configuration.maxSize(),
executorService,
configuration.refreshInterval(),
this::handleDatabaseChanged
);
this.objectMonitor = configuration.build(awsCredentialsProvider, executorService);
this.tableTag = tableTag; this.tableTag = tableTag;
this.routingTable.set(CallRoutingTable.empty()); this.routingTable.set(CallRoutingTable.empty());
this.refreshTimer = Metrics.timer(MetricsUtil.name(CallRoutingTableManager.class, tableTag)); this.refreshTimer = Metrics.timer(MetricsUtil.name(CallRoutingTableManager.class, tableTag));
@ -67,13 +57,11 @@ public class CallRoutingTableManager implements Supplier<CallRoutingTable>, Mana
@Override @Override
public void start() throws Exception { public void start() throws Exception {
Managed.super.start(); objectMonitor.start(this::handleDatabaseChanged);
objectMonitor.start();
} }
@Override @Override
public void stop() throws Exception { public void stop() throws Exception {
Managed.super.stop();
objectMonitor.stop(); objectMonitor.stop();
routingTable.getAndSet(null); routingTable.getAndSet(null);
} }

View File

@ -1,10 +1,14 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
public class AppConfigConfiguration { @JsonTypeName("default")
public class AppConfigConfiguration implements DynamicConfigurationManagerFactory {
@JsonProperty @JsonProperty
@NotEmpty @NotEmpty
@ -29,4 +33,11 @@ public class AppConfigConfiguration {
public String getConfigurationName() { public String getConfigurationName() {
return configuration; return configuration;
} }
@Override
public <T> DynamicConfigurationManager<T> build(Class<T> klazz, ScheduledExecutorService scheduledExecutorService,
AwsCredentialsProvider awsCredentialsProvider) {
return new DynamicConfigurationManager<>(application, environment, configuration, awsCredentialsProvider, klazz,
scheduledExecutorService);
}
} }

View File

@ -4,12 +4,11 @@
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record AwsAttachmentsConfiguration(@NotNull SecretString accessKey, public record AwsAttachmentsConfiguration(@NotNull @Valid StaticAwsCredentialsFactory credentials,
@NotNull SecretString accessSecret,
@NotBlank String bucket, @NotBlank String bucket,
@NotBlank String region) { @NotBlank String region) {
} }

View File

@ -0,0 +1,16 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = DefaultAwsCredentialsFactory.class)
public interface AwsCredentialsProviderFactory extends Discoverable {
AwsCredentialsProvider build();
}

View File

@ -32,9 +32,7 @@ public record BraintreeConfiguration(@NotBlank String merchantId,
@NotBlank String graphqlUrl, @NotBlank String graphqlUrl,
@NotEmpty Map<String, String> merchantAccounts, @NotEmpty Map<String, String> merchantAccounts,
@NotNull @Valid CircuitBreakerConfiguration circuitBreaker, @NotNull @Valid CircuitBreakerConfiguration circuitBreaker,
@NotBlank String pubSubProject, @Valid @NotNull PubSubPublisherFactory pubSubPublisher) {
@NotBlank String pubSubTopic,
@NotBlank String pubSubCredentialConfiguration) {
public BraintreeConfiguration { public BraintreeConfiguration {
if (circuitBreaker == null) { if (circuitBreaker == null) {

View File

@ -5,12 +5,11 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record CdnConfiguration(@NotNull SecretString accessKey, public record CdnConfiguration(@NotNull @Valid StaticAwsCredentialsFactory credentials,
@NotNull SecretString accessSecret,
@NotBlank String bucket, @NotBlank String bucket,
@NotBlank String region) { @NotBlank String region) {
} }

View File

@ -0,0 +1,19 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import io.micrometer.statsd.StatsdConfig;
import java.time.Duration;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = DogstatsdConfiguration.class)
public interface DatadogConfiguration extends StatsdConfig, Discoverable {
String getEnvironment();
Duration getShutdownWaitDuration();
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider;
@JsonTypeName("default")
public record DefaultAwsCredentialsFactory() implements AwsCredentialsProviderFactory {
public AwsCredentialsProvider build() {
return WebIdentityTokenFileCredentialsProvider.create();
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.api.gax.batching.BatchingSettings;
import com.google.api.gax.batching.FlowControlSettings;
import com.google.api.gax.batching.FlowController;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.cloud.pubsub.v1.Publisher;
import com.google.cloud.pubsub.v1.PublisherInterface;
import com.google.pubsub.v1.TopicName;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.validation.constraints.NotBlank;
@JsonTypeName("default")
public record DefaultPubSubPublisherFactory(@NotBlank String project,
@NotBlank String topic,
@NotBlank String credentialConfiguration) implements
PubSubPublisherFactory {
@Override
public PublisherInterface build() throws IOException {
final FlowControlSettings flowControlSettings = FlowControlSettings.newBuilder()
.setLimitExceededBehavior(FlowController.LimitExceededBehavior.ThrowException)
.setMaxOutstandingElementCount(100L)
.setMaxOutstandingRequestBytes(16 * 1024 * 1024L) // 16MB
.build();
final BatchingSettings batchingSettings = BatchingSettings.newBuilder()
.setFlowControlSettings(flowControlSettings)
.setDelayThreshold(org.threeten.bp.Duration.ofMillis(10))
// These thresholds are actually the default, setting them explicitly since creating a custom batchingSettings resets them
.setElementCountThreshold(100L)
.setRequestByteThreshold(5000L)
.build();
try (final ByteArrayInputStream credentialConfigInputStream =
new ByteArrayInputStream(credentialConfiguration.getBytes(StandardCharsets.UTF_8))) {
return Publisher.newBuilder(
TopicName.of(project, topic))
.setCredentialsProvider(
FixedCredentialsProvider.create(ExternalAccountCredentials.fromStream(credentialConfigInputStream)))
.setBatchingSettings(batchingSettings)
.build();
}
}
}

View File

@ -6,13 +6,14 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.micrometer.statsd.StatsdConfig; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.micrometer.statsd.StatsdFlavor; import io.micrometer.statsd.StatsdFlavor;
import java.time.Duration; import java.time.Duration;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
public class DogstatsdConfiguration implements StatsdConfig { @JsonTypeName("default")
public class DogstatsdConfiguration implements DatadogConfiguration {
@JsonProperty @JsonProperty
@NotNull @NotNull
@ -31,6 +32,7 @@ public class DogstatsdConfiguration implements StatsdConfig {
return step; return step;
} }
@Override
public String getEnvironment() { public String getEnvironment() {
return environment; return environment;
} }
@ -50,4 +52,9 @@ public class DogstatsdConfiguration implements StatsdConfig {
public String host() { public String host() {
return host; return host;
} }
@Override
public Duration getShutdownWaitDuration() {
return step().plus(step.dividedBy(2));
}
} }

View File

@ -0,0 +1,19 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import java.util.concurrent.ScheduledExecutorService;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = AppConfigConfiguration.class)
public interface DynamicConfigurationManagerFactory extends Discoverable {
<T> DynamicConfigurationManager<T> build(Class<T> configurationClass,
ScheduledExecutorService scheduledExecutorService, AwsCredentialsProvider awsCredentialsProvider);
}

View File

@ -9,11 +9,20 @@ import java.time.Duration;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive; import javax.validation.constraints.Positive;
import com.fasterxml.jackson.annotation.JsonTypeName;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.http.crt.AwsCrtHttpClient;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@JsonTypeName("default")
public record DynamoDbClientConfiguration(@NotBlank String region, public record DynamoDbClientConfiguration(@NotBlank String region,
@NotNull Duration clientExecutionTimeout, @NotNull Duration clientExecutionTimeout,
@NotNull Duration clientRequestTimeout, @NotNull Duration clientRequestTimeout,
@Positive int maxConnections) { @Positive int maxConnections) implements DynamoDbClientFactory {
public DynamoDbClientConfiguration { public DynamoDbClientConfiguration {
if (clientExecutionTimeout == null) { if (clientExecutionTimeout == null) {
@ -28,4 +37,32 @@ public record DynamoDbClientConfiguration(@NotBlank String region,
maxConnections = 50; maxConnections = 50;
} }
} }
@Override
public DynamoDbClient buildSyncClient(final AwsCredentialsProvider credentialsProvider) {
return DynamoDbClient.builder()
.region(Region.of(region()))
.credentialsProvider(credentialsProvider)
.overrideConfiguration(ClientOverrideConfiguration.builder()
.apiCallTimeout(clientExecutionTimeout())
.apiCallAttemptTimeout(clientRequestTimeout())
.build())
.httpClientBuilder(AwsCrtHttpClient.builder()
.maxConcurrency(maxConnections()))
.build();
}
@Override
public DynamoDbAsyncClient buildAsyncClient(final AwsCredentialsProvider credentialsProvider) {
return DynamoDbAsyncClient.builder()
.region(Region.of(region()))
.credentialsProvider(credentialsProvider)
.overrideConfiguration(ClientOverrideConfiguration.builder()
.apiCallTimeout(clientExecutionTimeout())
.apiCallAttemptTimeout(clientRequestTimeout())
.build())
.httpClientBuilder(NettyNioAsyncHttpClient.builder()
.maxConcurrency(maxConnections()))
.build();
}
} }

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = DynamoDbClientConfiguration.class)
public interface DynamoDbClientFactory extends Discoverable {
DynamoDbClient buildSyncClient(AwsCredentialsProvider awsCredentialsProvider);
DynamoDbAsyncClient buildAsyncClient(AwsCredentialsProvider awsCredentialsProvider);
}

View File

@ -0,0 +1,17 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import io.lettuce.core.resource.ClientResources;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = RedisClusterConfiguration.class)
public interface FaultTolerantRedisClusterFactory extends Discoverable {
FaultTolerantRedisCluster build(String name, ClientResources.Builder clientResourcesBuilder);
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import java.util.concurrent.ScheduledExecutorService;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = HCaptchaConfiguration.class)
public interface HCaptchaClientFactory extends Discoverable {
HCaptchaClient build(ScheduledExecutorService retryExecutor,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager);
}

View File

@ -7,9 +7,15 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import java.util.concurrent.ScheduledExecutorService;
public class HCaptchaConfiguration { @JsonTypeName("default")
public class HCaptchaConfiguration implements HCaptchaClientFactory {
@JsonProperty @JsonProperty
@NotNull @NotNull
@ -36,4 +42,14 @@ public class HCaptchaConfiguration {
return retry; return retry;
} }
@Override
public HCaptchaClient build(final ScheduledExecutorService retryExecutor,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
return new HCaptchaClient(
apiKey.value(),
retryExecutor,
circuitBreaker,
retry,
dynamicConfigurationManager);
}
} }

View File

@ -15,12 +15,12 @@ public class MessageCacheConfiguration {
@JsonProperty @JsonProperty
@NotNull @NotNull
@Valid @Valid
private RedisClusterConfiguration cluster; private FaultTolerantRedisClusterFactory cluster;
@JsonProperty @JsonProperty
private int persistDelayMinutes = 10; private int persistDelayMinutes = 10;
public RedisClusterConfiguration getRedisClusterConfiguration() { public FaultTolerantRedisClusterFactory getRedisClusterConfiguration() {
return cluster; return cluster;
} }

View File

@ -5,18 +5,23 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
@JsonTypeName("default")
public record MonitoredS3ObjectConfiguration( public record MonitoredS3ObjectConfiguration(
@NotBlank String s3Region, @NotBlank String s3Region,
@NotBlank String s3Bucket, @NotBlank String s3Bucket,
@NotBlank String objectKey, @NotBlank String objectKey,
Long maxSize, Long maxSize,
Duration refreshInterval) { Duration refreshInterval) implements S3ObjectMonitorFactory {
private static long DEFAULT_MAXSIZE = 16*1024*1024; private static final long DEFAULT_MAXSIZE = 16 * 1024 * 1024;
private static Duration DEFAULT_REFRESH_INTERVAL = Duration.ofMinutes(5); private static final Duration DEFAULT_REFRESH_INTERVAL = Duration.ofMinutes(5);
public MonitoredS3ObjectConfiguration { public MonitoredS3ObjectConfiguration {
if (maxSize == null) { if (maxSize == null) {
@ -26,4 +31,12 @@ public record MonitoredS3ObjectConfiguration(
refreshInterval = DEFAULT_REFRESH_INTERVAL; refreshInterval = DEFAULT_REFRESH_INTERVAL;
} }
} }
@Override
public S3ObjectMonitor build(final AwsCredentialsProvider awsCredentialsProvider,
final ScheduledExecutorService refreshExecutorService) {
return new S3ObjectMonitor(awsCredentialsProvider, s3Region, s3Bucket, objectKey, maxSize, refreshExecutorService,
refreshInterval);
}
} }

View File

@ -0,0 +1,33 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.net.http.HttpClient;
import java.util.Map;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
import org.whispersystems.textsecuregcm.currency.FixerClient;
@JsonTypeName("default")
public record PaymentsServiceClientsConfiguration(@NotNull SecretString coinMarketCapApiKey,
@NotNull SecretString fixerApiKey,
@NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds) implements
PaymentsServiceClientsFactory {
@Override
public FixerClient buildFixerClient(final HttpClient httpClient) {
return new FixerClient(httpClient, fixerApiKey.value());
}
@Override
public CoinMarketCapClient buildCoinMarketCapClient(final HttpClient httpClient) {
return new CoinMarketCapClient(httpClient, coinMarketCapApiKey.value(), coinMarketCapCurrencyIds);
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
import org.whispersystems.textsecuregcm.currency.FixerClient;
import java.net.http.HttpClient;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = PaymentsServiceClientsConfiguration.class)
public interface PaymentsServiceClientsFactory extends Discoverable {
FixerClient buildFixerClient(final HttpClient httpClient);
CoinMarketCapClient buildCoinMarketCapClient(HttpClient httpClient);
}

View File

@ -5,17 +5,15 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import java.util.List; import javax.validation.Valid;
import java.util.Map;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; import java.util.List;
public record PaymentsServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret, public record PaymentsServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret,
@NotNull SecretString coinMarketCapApiKey, @NotEmpty List<String> paymentCurrencies,
@NotNull SecretString fixerApiKey, @NotNull @Valid PaymentsServiceClientsFactory externalClients) {
@NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds,
@NotEmpty List<String> paymentCurrencies) {
} }

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public record ProvisioningConfiguration(@Valid @NotNull SingletonRedisClientFactory pubsub,
@Valid @NotNull CircuitBreakerConfiguration circuitBreaker) {
public ProvisioningConfiguration {
if (circuitBreaker == null) {
circuitBreaker = new CircuitBreakerConfiguration();
}
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.google.cloud.pubsub.v1.PublisherInterface;
import io.dropwizard.jackson.Discoverable;
import java.io.IOException;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = DefaultPubSubPublisherFactory.class)
public interface PubSubPublisherFactory extends Discoverable {
PublisherInterface build() throws IOException;
}

View File

@ -6,13 +6,17 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.annotations.VisibleForTesting;
import io.lettuce.core.resource.ClientResources;
import java.time.Duration;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.time.Duration; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
public class RedisClusterConfiguration { @JsonTypeName("default")
public class RedisClusterConfiguration implements FaultTolerantRedisClusterFactory {
@JsonProperty @JsonProperty
@NotEmpty @NotEmpty
@ -32,6 +36,11 @@ public class RedisClusterConfiguration {
@Valid @Valid
private RetryConfiguration retry = new RetryConfiguration(); private RetryConfiguration retry = new RetryConfiguration();
@VisibleForTesting
void setConfigurationUri(final String configurationUri) {
this.configurationUri = configurationUri;
}
public String getConfigurationUri() { public String getConfigurationUri() {
return configurationUri; return configurationUri;
} }
@ -47,4 +56,9 @@ public class RedisClusterConfiguration {
public RetryConfiguration getRetryConfiguration() { public RetryConfiguration getRetryConfiguration() {
return retry; return retry;
} }
@Override
public FaultTolerantRedisCluster build(final String name, final ClientResources.Builder clientResourcesBuilder) {
return new FaultTolerantRedisCluster(name, this, clientResourcesBuilder);
}
} }

View File

@ -6,13 +6,16 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.lettuce.core.RedisClient;
import io.lettuce.core.resource.ClientResources;
import java.time.Duration; import java.time.Duration;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.redis.RedisUriUtil;
public class RedisConfiguration { @JsonTypeName("default")
public class RedisConfiguration implements SingletonRedisClientFactory {
@JsonProperty @JsonProperty
@NotEmpty @NotEmpty
@ -22,11 +25,6 @@ public class RedisConfiguration {
@NotNull @NotNull
private Duration timeout = Duration.ofSeconds(1); private Duration timeout = Duration.ofSeconds(1);
@JsonProperty
@NotNull
@Valid
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
public String getUri() { public String getUri() {
return uri; return uri;
} }
@ -35,7 +33,12 @@ public class RedisConfiguration {
return timeout; return timeout;
} }
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() { @Override
return circuitBreaker; public RedisClient build(final ClientResources clientResources) {
final RedisClient redisClient = RedisClient.create(clientResources,
RedisUriUtil.createRedisUriWithTimeout(uri, timeout));
redisClient.setDefaultTimeout(timeout);
return redisClient;
} }
} }

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.core.setup.Environment;
import io.dropwizard.jackson.Discoverable;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = RegistrationServiceConfiguration.class)
public interface RegistrationServiceClientFactory extends Discoverable {
RegistrationServiceClient build(Environment environment, Executor callbackExecutor,
ScheduledExecutorService identityRefreshExecutor);
}

View File

@ -1,10 +1,35 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.core.setup.Environment;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import org.whispersystems.textsecuregcm.registration.IdentityTokenCallCredentials;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
@JsonTypeName("default")
public record RegistrationServiceConfiguration(@NotBlank String host, public record RegistrationServiceConfiguration(@NotBlank String host,
int port, int port,
@NotBlank String credentialConfigurationJson, @NotBlank String credentialConfigurationJson,
@NotBlank String identityTokenAudience, @NotBlank String identityTokenAudience,
@NotBlank String registrationCaCertificate) { @NotBlank String registrationCaCertificate) implements
RegistrationServiceClientFactory {
@Override
public RegistrationServiceClient build(final Environment environment, final Executor callbackExecutor,
final ScheduledExecutorService identityRefreshExecutor) {
try {
final IdentityTokenCallCredentials callCredentials = IdentityTokenCallCredentials.fromCredentialConfig(
credentialConfigurationJson, identityTokenAudience, identityRefreshExecutor);
environment.lifecycle().manage(callCredentials);
return new RegistrationServiceClient(host, port, callCredentials, registrationCaCertificate,
identityRefreshExecutor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -0,0 +1,19 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import java.util.concurrent.ScheduledExecutorService;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = MonitoredS3ObjectConfiguration.class)
public interface S3ObjectMonitorFactory extends Discoverable {
S3ObjectMonitor build(AwsCredentialsProvider awsCredentialsProvider,
ScheduledExecutorService refreshExecutorService);
}

View File

@ -0,0 +1,17 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import io.lettuce.core.RedisClient;
import io.lettuce.core.resource.ClientResources;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = RedisConfiguration.class)
public interface SingletonRedisClientFactory extends Discoverable {
RedisClient build(ClientResources clientResources);
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import javax.validation.constraints.NotNull;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = StaticAwsCredentialsFactory.class)
@JsonTypeName("static")
public record StaticAwsCredentialsFactory(@NotNull SecretString accessKeyId,
@NotNull SecretString secretAccessKey) implements
AwsCredentialsProviderFactory {
@Override
public AwsCredentialsProvider build() {
return StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKeyId.value(), secretAccessKey.value()));
}
}

View File

@ -7,25 +7,24 @@ package org.whispersystems.textsecuregcm.geo;
import com.maxmind.db.CHMCache; import com.maxmind.db.CHMCache;
import com.maxmind.geoip2.DatabaseReader; import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.GeoIp2Provider;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
public class MaxMindDatabaseManager implements Supplier<DatabaseReader>, Managed { public class MaxMindDatabaseManager implements Supplier<DatabaseReader>, Managed {
@ -39,21 +38,11 @@ public class MaxMindDatabaseManager implements Supplier<DatabaseReader>, Managed
private static final Logger log = LoggerFactory.getLogger(MaxMindDatabaseManager.class); private static final Logger log = LoggerFactory.getLogger(MaxMindDatabaseManager.class);
public MaxMindDatabaseManager( public MaxMindDatabaseManager(final ScheduledExecutorService executorService,
@Nonnull final ScheduledExecutorService executorService, final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration,
@Nonnull final MonitoredS3ObjectConfiguration configuration, final String databaseTag) {
@Nonnull final String databaseTag
){
this.databaseMonitor = new S3ObjectMonitor(
configuration.s3Region(),
configuration.s3Bucket(),
configuration.objectKey(),
configuration.maxSize(),
executorService,
configuration.refreshInterval(),
this::handleDatabaseChanged
);
this.databaseMonitor = configuration.build(awsCredentialsProvider, executorService);
this.databaseTag = databaseTag; this.databaseTag = databaseTag;
this.refreshTimer = Metrics.timer(MetricsUtil.name(MaxMindDatabaseManager.class, "refresh"), "db", databaseTag); this.refreshTimer = Metrics.timer(MetricsUtil.name(MaxMindDatabaseManager.class, "refresh"), "db", databaseTag);
} }
@ -93,17 +82,15 @@ public class MaxMindDatabaseManager implements Supplier<DatabaseReader>, Managed
@Override @Override
public void start() throws Exception { public void start() throws Exception {
Managed.super.start(); databaseMonitor.start(this::handleDatabaseChanged);
databaseMonitor.start();
} }
@Override @Override
public void stop() throws Exception { public void stop() throws Exception {
Managed.super.stop();
databaseMonitor.stop(); databaseMonitor.stop();
final DatabaseReader reader = databaseReader.getAndSet(null); final DatabaseReader reader = databaseReader.getAndSet(null);
if(reader != null) { if (reader != null) {
reader.close(); reader.close();
} }
} }

View File

@ -66,7 +66,8 @@ public class MetricsUtil {
environment.lifecycle().addEventListener(new ApplicationShutdownMonitor(Metrics.globalRegistry)); environment.lifecycle().addEventListener(new ApplicationShutdownMonitor(Metrics.globalRegistry));
environment.lifecycle().addEventListener( environment.lifecycle().addEventListener(
new MicrometerRegistryManager(Metrics.globalRegistry, config.getDatadogConfiguration().pollingFrequency())); new MicrometerRegistryManager(Metrics.globalRegistry,
config.getDatadogConfiguration().getShutdownWaitDuration()));
} }
@VisibleForTesting @VisibleForTesting

View File

@ -14,14 +14,13 @@ import org.slf4j.LoggerFactory;
public class MicrometerRegistryManager implements LifeCycle.Listener { public class MicrometerRegistryManager implements LifeCycle.Listener {
private static final Logger logger = LoggerFactory.getLogger(MicrometerRegistryManager.class); private static final Logger logger = LoggerFactory.getLogger(MicrometerRegistryManager.class);
private static final Duration BUFFER = Duration.ofSeconds(5);
private final MeterRegistry meterRegistry; private final MeterRegistry meterRegistry;
private final Duration waitDuration; private final Duration waitDuration;
public MicrometerRegistryManager(final MeterRegistry meterRegistry, final Duration pollingFrequency) { public MicrometerRegistryManager(final MeterRegistry meterRegistry, final Duration waitDuration) {
this.meterRegistry = meterRegistry; this.meterRegistry = meterRegistry;
this.waitDuration = pollingFrequency.plus(BUFFER); this.waitDuration = waitDuration;
} }
@Override @Override

View File

@ -7,7 +7,6 @@ package org.whispersystems.textsecuregcm.push;
import static com.codahale.metrics.MetricRegistry.name; import static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
@ -17,11 +16,9 @@ import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.codec.ByteArrayCodec; import io.lettuce.core.codec.ByteArrayCodec;
import io.lettuce.core.pubsub.RedisPubSubAdapter; import io.lettuce.core.pubsub.RedisPubSubAdapter;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.resource.ClientResources;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Tags;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -29,7 +26,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
import org.whispersystems.textsecuregcm.redis.RedisOperation; import org.whispersystems.textsecuregcm.redis.RedisOperation;
import org.whispersystems.textsecuregcm.redis.RedisUriUtil;
import org.whispersystems.textsecuregcm.storage.PubSubProtos; import org.whispersystems.textsecuregcm.storage.PubSubProtos;
import org.whispersystems.textsecuregcm.util.CircuitBreakerUtil; import org.whispersystems.textsecuregcm.util.CircuitBreakerUtil;
import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException;
@ -56,22 +52,10 @@ public class ProvisioningManager extends RedisPubSubAdapter<byte[], byte[]> impl
private static final Logger logger = LoggerFactory.getLogger(ProvisioningManager.class); private static final Logger logger = LoggerFactory.getLogger(ProvisioningManager.class);
public ProvisioningManager(final String redisUri, public ProvisioningManager(final RedisClient redisClient,
final ClientResources clientResources,
final Duration timeout,
final CircuitBreakerConfiguration circuitBreakerConfiguration) {
this(RedisClient.create(clientResources, RedisUriUtil.createRedisUriWithTimeout(redisUri, timeout)), timeout,
circuitBreakerConfiguration);
}
@VisibleForTesting
ProvisioningManager(final RedisClient redisClient,
final Duration timeout,
final CircuitBreakerConfiguration circuitBreakerConfiguration) { final CircuitBreakerConfiguration circuitBreakerConfiguration) {
this.redisClient = redisClient; this.redisClient = redisClient;
this.redisClient.setDefaultTimeout(timeout);
this.subscriptionConnection = redisClient.connectPubSub(new ByteArrayCodec()); this.subscriptionConnection = redisClient.connectPubSub(new ByteArrayCodec());
this.publicationConnection = redisClient.connect(new ByteArrayCodec()); this.publicationConnection = redisClient.connect(new ByteArrayCodec());

View File

@ -7,13 +7,13 @@ package org.whispersystems.textsecuregcm.registration;
import com.google.auth.oauth2.ExternalAccountCredentials; import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.auth.oauth2.ImpersonatedCredentials; import com.google.auth.oauth2.ImpersonatedCredentials;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.lifecycle.Managed;
import io.github.resilience4j.core.IntervalFunction; import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig; import io.github.resilience4j.retry.RetryConfig;
import io.grpc.CallCredentials; import io.grpc.CallCredentials;
import io.grpc.Metadata; import io.grpc.Metadata;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
@ -28,7 +28,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
class IdentityTokenCallCredentials extends CallCredentials implements Closeable { public class IdentityTokenCallCredentials extends CallCredentials implements Managed {
private static final Duration IDENTITY_TOKEN_LIFETIME = Duration.ofHours(1); private static final Duration IDENTITY_TOKEN_LIFETIME = Duration.ofHours(1);
private static final Duration IDENTITY_TOKEN_REFRESH_BUFFER = Duration.ofMinutes(10); private static final Duration IDENTITY_TOKEN_REFRESH_BUFFER = Duration.ofMinutes(10);
@ -58,7 +58,7 @@ class IdentityTokenCallCredentials extends CallCredentials implements Closeable
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
} }
static IdentityTokenCallCredentials fromCredentialConfig( public static IdentityTokenCallCredentials fromCredentialConfig(
final String credentialConfigJson, final String credentialConfigJson,
final String audience, final String audience,
final ScheduledExecutorService scheduledExecutorService) throws IOException { final ScheduledExecutorService scheduledExecutorService) throws IOException {
@ -129,7 +129,7 @@ class IdentityTokenCallCredentials extends CallCredentials implements Closeable
} }
@Override @Override
public void close() { public void stop() {
synchronized (this) { synchronized (this) {
if (!scheduledFuture.isDone()) { if (!scheduledFuture.isDone()) {
scheduledFuture.cancel(true); scheduledFuture.cancel(true);

View File

@ -8,6 +8,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber; import com.google.i18n.phonenumbers.Phonenumber;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.grpc.CallCredentials;
import io.grpc.ChannelCredentials; import io.grpc.ChannelCredentials;
import io.grpc.Deadline; import io.grpc.Deadline;
import io.grpc.Grpc; import io.grpc.Grpc;
@ -21,7 +22,6 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -38,7 +38,6 @@ import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
public class RegistrationServiceClient implements Managed { public class RegistrationServiceClient implements Managed {
private final ManagedChannel channel; private final ManagedChannel channel;
private final IdentityTokenCallCredentials identityTokenCallCredentials;
private final RegistrationServiceGrpc.RegistrationServiceFutureStub stub; private final RegistrationServiceGrpc.RegistrationServiceFutureStub stub;
private final Executor callbackExecutor; private final Executor callbackExecutor;
@ -61,11 +60,9 @@ public class RegistrationServiceClient implements Managed {
public RegistrationServiceClient(final String host, public RegistrationServiceClient(final String host,
final int port, final int port,
final String credentialConfigJson, final CallCredentials callCredentials,
final String identityTokenAudience,
final String caCertificatePem, final String caCertificatePem,
final Executor callbackExecutor, final Executor callbackExecutor) throws IOException {
final ScheduledExecutorService identityRefreshExecutor) throws IOException {
try (final ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(caCertificatePem.getBytes(StandardCharsets.UTF_8))) { try (final ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(caCertificatePem.getBytes(StandardCharsets.UTF_8))) {
final ChannelCredentials tlsChannelCredentials = TlsChannelCredentials.newBuilder() final ChannelCredentials tlsChannelCredentials = TlsChannelCredentials.newBuilder()
@ -77,10 +74,7 @@ public class RegistrationServiceClient implements Managed {
.build(); .build();
} }
this.identityTokenCallCredentials = IdentityTokenCallCredentials.fromCredentialConfig( this.stub = RegistrationServiceGrpc.newFutureStub(channel).withCallCredentials(callCredentials);
credentialConfigJson, identityTokenAudience, identityRefreshExecutor);
this.stub = RegistrationServiceGrpc.newFutureStub(channel).withCallCredentials(identityTokenCallCredentials);
this.callbackExecutor = callbackExecutor; this.callbackExecutor = callbackExecutor;
} }
@ -284,6 +278,5 @@ public class RegistrationServiceClient implements Managed {
if (channel != null) { if (channel != null) {
channel.shutdown(); channel.shutdown();
} }
this.identityTokenCallCredentials.close();
} }
} }

View File

@ -1,22 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.s3;
import io.dropwizard.lifecycle.Managed;
import java.util.function.Supplier;
public interface ManagedSupplier<T> extends Supplier<T>, Managed {
@Override
default void start() throws Exception {
// noop
}
@Override
default void stop() throws Exception {
// noop
}
}

View File

@ -1,93 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.s3;
import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
public class S3MonitoringSupplier<T> implements ManagedSupplier<T> {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Nonnull
private final Timer refreshTimer;
@Nonnull
private final Counter refreshErrors;
@Nonnull
private final AtomicReference<T> holder;
@Nonnull
private final S3ObjectMonitor monitor;
@Nonnull
private final Function<InputStream, T> parser;
public S3MonitoringSupplier(
@Nonnull final ScheduledExecutorService executor,
@Nonnull final MonitoredS3ObjectConfiguration cfg,
@Nonnull final Function<InputStream, T> parser,
@Nonnull final T initial,
@Nonnull final String name) {
this.refreshTimer = Metrics.timer(name(name, "refresh"));
this.refreshErrors = Metrics.counter(name(name, "refreshErrors"));
this.holder = new AtomicReference<>(initial);
this.parser = requireNonNull(parser);
this.monitor = new S3ObjectMonitor(
cfg.s3Region(),
cfg.s3Bucket(),
cfg.objectKey(),
cfg.maxSize(),
executor,
cfg.refreshInterval(),
this::handleObjectChange
);
}
@Override
@Nonnull
public T get() {
return requireNonNull(holder.get());
}
@Override
public void start() throws Exception {
monitor.start();
}
@Override
public void stop() throws Exception {
monitor.stop();
}
private void handleObjectChange(@Nonnull final InputStream inputStream) {
refreshTimer.record(() -> {
// parser function is supposed to close the input stream
try {
holder.set(parser.apply(inputStream));
} catch (final Exception e) {
log.error("failed to update internal state from the monitored object", e);
refreshErrors.increment();
}
});
}
}

View File

@ -17,7 +17,7 @@ import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.WhisperServerService; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Client;
@ -39,8 +39,6 @@ public class S3ObjectMonitor {
private final Duration refreshInterval; private final Duration refreshInterval;
private ScheduledFuture<?> refreshFuture; private ScheduledFuture<?> refreshFuture;
private final Consumer<InputStream> changeListener;
private final AtomicReference<String> lastETag = new AtomicReference<>(); private final AtomicReference<String> lastETag = new AtomicReference<>();
private final S3Client s3Client; private final S3Client s3Client;
@ -48,24 +46,23 @@ public class S3ObjectMonitor {
private static final Logger log = LoggerFactory.getLogger(S3ObjectMonitor.class); private static final Logger log = LoggerFactory.getLogger(S3ObjectMonitor.class);
public S3ObjectMonitor( public S3ObjectMonitor(
final AwsCredentialsProvider awsCredentialsProvider,
final String s3Region, final String s3Region,
final String s3Bucket, final String s3Bucket,
final String objectKey, final String objectKey,
final long maxObjectSize, final long maxObjectSize,
final ScheduledExecutorService refreshExecutorService, final ScheduledExecutorService refreshExecutorService,
final Duration refreshInterval, final Duration refreshInterval) {
final Consumer<InputStream> changeListener) {
this(S3Client.builder() this(S3Client.builder()
.region(Region.of(s3Region)) .region(Region.of(s3Region))
.credentialsProvider(WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER) .credentialsProvider(awsCredentialsProvider)
.build(), .build(),
s3Bucket, s3Bucket,
objectKey, objectKey,
maxObjectSize, maxObjectSize,
refreshExecutorService, refreshExecutorService,
refreshInterval, refreshInterval);
changeListener);
} }
@VisibleForTesting @VisibleForTesting
@ -75,8 +72,7 @@ public class S3ObjectMonitor {
final String objectKey, final String objectKey,
final long maxObjectSize, final long maxObjectSize,
final ScheduledExecutorService refreshExecutorService, final ScheduledExecutorService refreshExecutorService,
final Duration refreshInterval, final Duration refreshInterval) {
final Consumer<InputStream> changeListener) {
this.s3Client = s3Client; this.s3Client = s3Client;
this.s3Bucket = s3Bucket; this.s3Bucket = s3Bucket;
@ -85,21 +81,19 @@ public class S3ObjectMonitor {
this.refreshExecutorService = refreshExecutorService; this.refreshExecutorService = refreshExecutorService;
this.refreshInterval = refreshInterval; this.refreshInterval = refreshInterval;
this.changeListener = changeListener;
} }
public synchronized void start() { public synchronized void start(final Consumer<InputStream> changeListener) {
if (refreshFuture != null) { if (refreshFuture != null) {
throw new RuntimeException("S3 object manager already started"); throw new RuntimeException("S3 object manager already started");
} }
// Run the first request immediately/blocking, then start subsequent calls. // Run the first request immediately/blocking, then start subsequent calls.
log.info("Initial request for s3://{}/{}", s3Bucket, objectKey); log.info("Initial request for s3://{}/{}", s3Bucket, objectKey);
refresh(); refresh(changeListener);
refreshFuture = refreshExecutorService refreshFuture = refreshExecutorService
.scheduleAtFixedRate(this::refresh, refreshInterval.toMillis(), refreshInterval.toMillis(), .scheduleAtFixedRate(() -> refresh(changeListener), refreshInterval.toMillis(), refreshInterval.toMillis(),
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
} }
@ -139,7 +133,7 @@ public class S3ObjectMonitor {
* changed since the last call to {@link #getObject()} or {@code refresh()}. * changed since the last call to {@link #getObject()} or {@code refresh()}.
*/ */
@VisibleForTesting @VisibleForTesting
void refresh() { void refresh(final Consumer<InputStream> changeListener) {
try { try {
final HeadObjectResponse objectMetadata = s3Client.headObject(HeadObjectRequest.builder() final HeadObjectResponse objectMetadata = s3Client.headObject(HeadObjectRequest.builder()
.bucket(s3Bucket) .bucket(s3Bucket)

View File

@ -24,6 +24,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient;
import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest;
@ -54,9 +55,11 @@ public class DynamicConfigurationManager<T> {
private static final Logger logger = LoggerFactory.getLogger(DynamicConfigurationManager.class); private static final Logger logger = LoggerFactory.getLogger(DynamicConfigurationManager.class);
public DynamicConfigurationManager(String application, String environment, String configurationName, public DynamicConfigurationManager(String application, String environment, String configurationName,
Class<T> configurationClass, ScheduledExecutorService scheduledExecutorService) { AwsCredentialsProvider awsCredentialsProvider, Class<T> configurationClass,
ScheduledExecutorService scheduledExecutorService) {
this(AppConfigDataClient this(AppConfigDataClient
.builder() .builder()
.credentialsProvider(awsCredentialsProvider)
.overrideConfiguration(ClientOverrideConfiguration.builder() .overrideConfiguration(ClientOverrideConfiguration.builder()
.apiCallTimeout(Duration.ofSeconds(10)) .apiCallTimeout(Duration.ofSeconds(10))
.apiCallAttemptTimeout(Duration.ofSeconds(10)).build()) .apiCallAttemptTimeout(Duration.ofSeconds(10)).build())
@ -86,6 +89,9 @@ public class DynamicConfigurationManager<T> {
} }
public void start() { public void start() {
if (initialized.getCount() == 0) {
return;
}
configuration.set(retrieveInitialDynamicConfiguration()); configuration.set(retrieveInitialDynamicConfiguration());
initialized.countDown(); initialized.countDown();

View File

@ -19,8 +19,10 @@ import com.braintreegateway.TransactionSearchRequest;
import com.braintreegateway.exceptions.BraintreeException; import com.braintreegateway.exceptions.BraintreeException;
import com.braintreegateway.exceptions.NotFoundException; import com.braintreegateway.exceptions.NotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.cloud.pubsub.v1.Publisher; import com.google.cloud.pubsub.v1.PublisherInterface;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.pubsub.v1.PubsubMessage;
import io.micrometer.core.instrument.Metrics;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@ -40,8 +42,6 @@ import javax.annotation.Nullable;
import javax.ws.rs.ClientErrorException; import javax.ws.rs.ClientErrorException;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import com.google.pubsub.v1.PubsubMessage;
import io.micrometer.core.instrument.Metrics;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
@ -65,7 +65,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
private final BraintreeGateway braintreeGateway; private final BraintreeGateway braintreeGateway;
private final BraintreeGraphqlClient braintreeGraphqlClient; private final BraintreeGraphqlClient braintreeGraphqlClient;
private final CurrencyConversionManager currencyConversionManager; private final CurrencyConversionManager currencyConversionManager;
private final Publisher pubsubPublisher; private final PublisherInterface pubsubPublisher;
private final Executor executor; private final Executor executor;
private final Map<PaymentMethod, Set<String>> supportedCurrenciesByPaymentMethod; private final Map<PaymentMethod, Set<String>> supportedCurrenciesByPaymentMethod;
private final Map<String, String> currenciesToMerchantAccounts; private final Map<String, String> currenciesToMerchantAccounts;
@ -79,7 +79,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
final Map<String, String> currenciesToMerchantAccounts, final Map<String, String> currenciesToMerchantAccounts,
final String graphqlUri, final String graphqlUri,
final CurrencyConversionManager currencyConversionManager, final CurrencyConversionManager currencyConversionManager,
final Publisher pubsubPublisher, final PublisherInterface pubsubPublisher,
final CircuitBreakerConfiguration circuitBreakerConfiguration, final CircuitBreakerConfiguration circuitBreakerConfiguration,
final Executor executor, final Executor executor,
final ScheduledExecutorService retryExecutor) { final ScheduledExecutorService retryExecutor) {
@ -107,7 +107,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
BraintreeManager(final BraintreeGateway braintreeGateway, BraintreeManager(final BraintreeGateway braintreeGateway,
final Map<PaymentMethod, Set<String>> supportedCurrenciesByPaymentMethod, final Map<PaymentMethod, Set<String>> supportedCurrenciesByPaymentMethod,
final Map<String, String> currenciesToMerchantAccounts, final BraintreeGraphqlClient braintreeGraphqlClient, final Map<String, String> currenciesToMerchantAccounts, final BraintreeGraphqlClient braintreeGraphqlClient,
final CurrencyConversionManager currencyConversionManager, final Publisher pubsubPublisher, final CurrencyConversionManager currencyConversionManager, final PublisherInterface pubsubPublisher,
final Executor executor) { final Executor executor) {
this.braintreeGateway = braintreeGateway; this.braintreeGateway = braintreeGateway;
this.supportedCurrenciesByPaymentMethod = supportedCurrenciesByPaymentMethod; this.supportedCurrenciesByPaymentMethod = supportedCurrenciesByPaymentMethod;

View File

@ -1,42 +0,0 @@
package org.whispersystems.textsecuregcm.util;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.http.crt.AwsCrtHttpClient;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
public class DynamoDbFromConfig {
public static DynamoDbClient client(DynamoDbClientConfiguration config, AwsCredentialsProvider credentialsProvider) {
return DynamoDbClient.builder()
.region(Region.of(config.region()))
.credentialsProvider(credentialsProvider)
.overrideConfiguration(ClientOverrideConfiguration.builder()
.apiCallTimeout(config.clientExecutionTimeout())
.apiCallAttemptTimeout(config.clientRequestTimeout())
.build())
.httpClientBuilder(AwsCrtHttpClient
.builder()
.maxConcurrency(config.maxConnections()))
.build();
}
public static DynamoDbAsyncClient asyncClient(
DynamoDbClientConfiguration config,
AwsCredentialsProvider credentialsProvider) {
return DynamoDbAsyncClient.builder()
.region(Region.of(config.region()))
.credentialsProvider(credentialsProvider)
.overrideConfiguration(ClientOverrideConfiguration.builder()
.apiCallTimeout(config.clientExecutionTimeout())
.apiCallAttemptTimeout(config.clientRequestTimeout())
.build())
.httpClientBuilder(NettyNioAsyncHttpClient.builder()
.maxConcurrency(config.maxConnections()))
.build();
}
}

View File

@ -23,7 +23,6 @@ import java.util.concurrent.ScheduledExecutorService;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.WhisperServerService;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
@ -50,10 +49,10 @@ import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb;
import org.whispersystems.textsecuregcm.storage.ReportMessageManager; import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
import org.whispersystems.textsecuregcm.storage.UsernameHashNotAvailableException; import org.whispersystems.textsecuregcm.storage.UsernameHashNotAvailableException;
import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException; import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import org.whispersystems.textsecuregcm.util.ExceptionUtils; import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@ -97,18 +96,19 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
throws Exception { throws Exception {
environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final AwsCredentialsProvider awsCredentialsProvider = configuration.getAwsCredentialsConfiguration().build();
ScheduledExecutorService dynamicConfigurationExecutor = environment.lifecycle() ScheduledExecutorService dynamicConfigurationExecutor = environment.lifecycle()
.scheduledExecutorService(name(getClass(), "dynamicConfiguration-%d")).threads(1).build(); .scheduledExecutorService(name(getClass(), "dynamicConfiguration-%d")).threads(1).build();
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>( DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = configuration.getAppConfig().build(
configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), DynamicConfiguration.class, dynamicConfigurationExecutor, awsCredentialsProvider);
configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class, dynamicConfigurationExecutor);
dynamicConfigurationManager.start(); dynamicConfigurationManager.start();
final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder(); final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder();
FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", FaultTolerantRedisCluster cacheCluster = configuration.getCacheClusterConfiguration().build("main_cache_cluster",
configuration.getCacheClusterConfiguration(), redisClientResourcesBuilder); redisClientResourcesBuilder);
Scheduler messageDeliveryScheduler = Schedulers.fromExecutorService( Scheduler messageDeliveryScheduler = Schedulers.fromExecutorService(
environment.lifecycle().executorService("messageDelivery-%d").maxThreads(4) environment.lifecycle().executorService("messageDelivery-%d").maxThreads(4)
@ -135,11 +135,11 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
ExternalServiceCredentialsGenerator secureValueRecoveryCredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator( ExternalServiceCredentialsGenerator secureValueRecoveryCredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator(
configuration.getSvr2Configuration()); configuration.getSvr2Configuration());
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( DynamoDbAsyncClient dynamoDbAsyncClient = configuration.getDynamoDbClientConfiguration()
configuration.getDynamoDbClientConfiguration(), WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER); .buildAsyncClient(awsCredentialsProvider);
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(configuration.getDynamoDbClientConfiguration(), DynamoDbClient dynamoDbClient = configuration.getDynamoDbClientConfiguration()
WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER); .buildSyncClient(awsCredentialsProvider);
RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(), configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(),
@ -173,12 +173,12 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
configuration.getDynamoDbTables().getMessages().getTableName(), configuration.getDynamoDbTables().getMessages().getTableName(),
configuration.getDynamoDbTables().getMessages().getExpiration(), configuration.getDynamoDbTables().getMessages().getExpiration(),
messageDeletionExecutor); messageDeletionExecutor);
FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages", FaultTolerantRedisCluster messagesCluster = configuration.getMessageCacheConfiguration()
configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClientResourcesBuilder); .getRedisClusterConfiguration().build("messages", redisClientResourcesBuilder);
FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence", FaultTolerantRedisCluster clientPresenceCluster = configuration.getClientPresenceClusterConfiguration()
configuration.getClientPresenceClusterConfiguration(), redisClientResourcesBuilder); .build("client_presence", redisClientResourcesBuilder);
FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", FaultTolerantRedisCluster rateLimitersCluster = configuration.getRateLimitersCluster().build("rate_limiters",
configuration.getRateLimitersCluster(), redisClientResourcesBuilder); redisClientResourcesBuilder);
SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client( SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client(
secureValueRecoveryCredentialsGenerator, secureValueRecoveryExecutor, secureValueRecoveryServiceRetryExecutor, secureValueRecoveryCredentialsGenerator, secureValueRecoveryExecutor, secureValueRecoveryServiceRetryExecutor,
configuration.getSvr2Configuration()); configuration.getSvr2Configuration());

View File

@ -18,7 +18,6 @@ import java.util.concurrent.ScheduledExecutorService;
import org.signal.libsignal.zkgroup.GenericServerSecretParams; import org.signal.libsignal.zkgroup.GenericServerSecretParams;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.WhisperServerService;
import org.whispersystems.textsecuregcm.attachments.TusAttachmentGenerator; import org.whispersystems.textsecuregcm.attachments.TusAttachmentGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.backup.BackupManager; import org.whispersystems.textsecuregcm.backup.BackupManager;
@ -49,10 +48,10 @@ import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb; import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb;
import org.whispersystems.textsecuregcm.storage.ReportMessageManager; import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import org.whispersystems.textsecuregcm.util.ManagedAwsCrt; import org.whispersystems.textsecuregcm.util.ManagedAwsCrt;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@ -80,20 +79,21 @@ record CommandDependencies(
environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final AwsCredentialsProvider awsCredentialsProvider = configuration.getAwsCredentialsConfiguration().build();
ScheduledExecutorService dynamicConfigurationExecutor = environment.lifecycle() ScheduledExecutorService dynamicConfigurationExecutor = environment.lifecycle()
.scheduledExecutorService(name(name, "dynamicConfiguration-%d")).threads(1).build(); .scheduledExecutorService(name(name, "dynamicConfiguration-%d")).threads(1).build();
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>( DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = configuration.getAppConfig().build(
configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), DynamicConfiguration.class, dynamicConfigurationExecutor, awsCredentialsProvider);
configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class, dynamicConfigurationExecutor);
dynamicConfigurationManager.start(); dynamicConfigurationManager.start();
MetricsUtil.configureRegistries(configuration, environment, dynamicConfigurationManager); MetricsUtil.configureRegistries(configuration, environment, dynamicConfigurationManager);
final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder(); final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder();
FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache", FaultTolerantRedisCluster cacheCluster = configuration.getCacheClusterConfiguration().build("main_cache",
configuration.getCacheClusterConfiguration(), redisClientResourcesBuilder); redisClientResourcesBuilder);
ScheduledExecutorService recurringJobExecutor = environment.lifecycle() ScheduledExecutorService recurringJobExecutor = environment.lifecycle()
.scheduledExecutorService(name(name, "recurringJob-%d")).threads(2).build(); .scheduledExecutorService(name(name, "recurringJob-%d")).threads(2).build();
@ -124,11 +124,11 @@ record CommandDependencies(
ExternalServiceCredentialsGenerator secureValueRecoveryCredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator( ExternalServiceCredentialsGenerator secureValueRecoveryCredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator(
configuration.getSvr2Configuration()); configuration.getSvr2Configuration());
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( DynamoDbAsyncClient dynamoDbAsyncClient = configuration.getDynamoDbClientConfiguration()
configuration.getDynamoDbClientConfiguration(), WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER); .buildAsyncClient(awsCredentialsProvider);
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( DynamoDbClient dynamoDbClient = configuration.getDynamoDbClientConfiguration()
configuration.getDynamoDbClientConfiguration(), WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER); .buildSyncClient(awsCredentialsProvider);
RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(), configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(),
@ -163,12 +163,12 @@ record CommandDependencies(
configuration.getDynamoDbTables().getMessages().getTableName(), configuration.getDynamoDbTables().getMessages().getTableName(),
configuration.getDynamoDbTables().getMessages().getExpiration(), configuration.getDynamoDbTables().getMessages().getExpiration(),
messageDeletionExecutor); messageDeletionExecutor);
FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages", FaultTolerantRedisCluster messagesCluster = configuration.getMessageCacheConfiguration()
configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClientResourcesBuilder); .getRedisClusterConfiguration().build("messages", redisClientResourcesBuilder);
FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence", FaultTolerantRedisCluster clientPresenceCluster = configuration.getClientPresenceClusterConfiguration()
configuration.getClientPresenceClusterConfiguration(), redisClientResourcesBuilder); .build("client_presence", redisClientResourcesBuilder);
FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", FaultTolerantRedisCluster rateLimitersCluster = configuration.getRateLimitersCluster().build("rate_limiters",
configuration.getRateLimitersCluster(), redisClientResourcesBuilder); redisClientResourcesBuilder);
SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client( SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client(
secureValueRecoveryCredentialsGenerator, secureValueRecoveryServiceExecutor, secureValueRecoveryCredentialsGenerator, secureValueRecoveryServiceExecutor,
secureValueRecoveryServiceRetryExecutor, secureValueRecoveryServiceRetryExecutor,

View File

@ -63,8 +63,8 @@ public class ScheduledApnPushNotificationSenderServiceCommand extends ServerComm
}); });
} }
final FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", final FaultTolerantRedisCluster pushSchedulerCluster = configuration.getPushSchedulerCluster()
configuration.getPushSchedulerCluster(), deps.redisClusterClientResourcesBuilder()); .build("push_scheduler", deps.redisClusterClientResourcesBuilder());
final ExecutorService apnSenderExecutor = environment.lifecycle().executorService(name(getClass(), "apnSender-%d")) final ExecutorService apnSenderExecutor = environment.lifecycle().executorService(name(getClass(), "apnSender-%d"))
.maxThreads(1).minThreads(1).build(); .maxThreads(1).minThreads(1).build();

View File

@ -0,0 +1,11 @@
org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory
org.whispersystems.textsecuregcm.configuration.DatadogConfiguration
org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory
org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory
org.whispersystems.textsecuregcm.configuration.HCaptchaClientFactory
org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory
org.whispersystems.textsecuregcm.configuration.PaymentsServiceClientsFactory
org.whispersystems.textsecuregcm.configuration.PubSubPublisherFactory
org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory
org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory
org.whispersystems.textsecuregcm.configuration.SingletonRedisClientFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StaticAwsCredentialsFactory

View File

@ -0,0 +1,28 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
import io.dropwizard.util.Resources;
/**
* This class may be run directly from a correctly configured IDE, or using the command line:
* <p>
* <code>./mvnw clean integration-test -DskipTests=true -Ptest-server</code>
* <p>
* <strong>NOTE: many features are non-functional, especially those that depend on external services</strong>
*/
public class LocalWhisperServerService {
public static void main(String[] args) throws Exception {
System.setProperty("secrets.bundle.filename",
Resources.getResource("config/test-secrets-bundle.yml").getPath());
System.setProperty("sqlite.dir", "service/target/lib");
System.setProperty("aws.region", "local-test-region");
new WhisperServerService().run("server", Resources.getResource("config/test.yml").getPath());
}
}

View File

@ -0,0 +1,145 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.dropwizard.testing.junit5.DropwizardAppExtension;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.util.Resources;
import java.net.URI;
import java.util.Map;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema;
import org.whispersystems.textsecuregcm.tests.util.TestWebsocketListener;
import org.whispersystems.textsecuregcm.util.AttributeValues;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
@ExtendWith(DropwizardExtensionsSupport.class)
class WhisperServerServiceTest {
static {
System.setProperty("secrets.bundle.filename",
Resources.getResource("config/test-secrets-bundle.yml").getPath());
// needed for AppConfigDataClient initialization
System.setProperty("aws.region", "local-test-region");
}
private static final DropwizardAppExtension<WhisperServerConfiguration> EXTENSION = new DropwizardAppExtension<>(
WhisperServerService.class, Resources.getResource("config/test.yml").getPath());
private WebSocketClient webSocketClient;
@AfterAll
static void teardown() {
System.clearProperty("secrets.bundle.filename");
System.clearProperty("aws.region");
}
@BeforeEach
void setUp() throws Exception {
webSocketClient = new WebSocketClient();
webSocketClient.start();
}
@AfterEach
void tearDown() throws Exception {
webSocketClient.stop();
}
@Test
void start() throws Exception {
// make sure the service nominally starts and responds to health checks
Client client = EXTENSION.client();
final Response ping = client.target(
String.format("http://localhost:%d%s", EXTENSION.getAdminPort(), "/ping"))
.request("application/json")
.get();
assertEquals(200, ping.getStatus());
final Response healthCheck = client.target(
String.format("http://localhost:%d%s", EXTENSION.getLocalPort(), "/health-check"))
.request("application/json")
.get();
assertEquals(200, healthCheck.getStatus());
}
@Test
void websocket() throws Exception {
// test unauthenticated websocket
final TestWebsocketListener testWebsocketListener = new TestWebsocketListener();
webSocketClient.connect(testWebsocketListener,
URI.create(String.format("ws://localhost:%d/v1/websocket/", EXTENSION.getLocalPort())))
.join();
final WebSocketResponseMessage keepAlive = testWebsocketListener.doGet("/v1/keepalive").join();
assertEquals(200, keepAlive.getStatus());
}
@Test
void dynamoDb() {
// confirm that local dynamodb nominally works
final AwsCredentialsProvider awsCredentialsProvider = EXTENSION.getConfiguration().getAwsCredentialsConfiguration()
.build();
try (DynamoDbClient dynamoDbClient = EXTENSION.getConfiguration().getDynamoDbClientConfiguration()
.buildSyncClient(awsCredentialsProvider)) {
final DynamoDbExtension.TableSchema numbers = DynamoDbExtensionSchema.Tables.NUMBERS;
final AttributeValue numberAV = AttributeValues.s("+12125550001");
final GetItemResponse notFoundResponse = dynamoDbClient.getItem(GetItemRequest.builder()
.tableName(numbers.tableName())
.key(Map.of(numbers.hashKeyName(), numberAV))
.build());
assertFalse(notFoundResponse.hasItem());
dynamoDbClient.putItem(PutItemRequest.builder()
.tableName(numbers.tableName())
.item(Map.of(numbers.hashKeyName(), numberAV))
.build());
final GetItemResponse foundResponse = dynamoDbClient.getItem(GetItemRequest.builder()
.tableName(numbers.tableName())
.key(Map.of(numbers.hashKeyName(), numberAV))
.build());
assertTrue(foundResponse.hasItem());
dynamoDbClient.deleteItem(DeleteItemRequest.builder()
.tableName(numbers.tableName())
.key(Map.of(numbers.hashKeyName(), numberAV))
.build());
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@JsonTypeName("local")
public class LocalDynamoDbFactory implements DynamoDbClientFactory {
private static final DynamoDbExtension EXTENSION = new DynamoDbExtension(System.getProperty("sqlite.dir"),
DynamoDbExtensionSchema.Tables.values());
static {
try {
EXTENSION.beforeEach(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> EXTENSION.afterEach(null)));
}
@Override
public DynamoDbClient buildSyncClient(final AwsCredentialsProvider awsCredentialsProvider) {
return EXTENSION.getDynamoDbClient();
}
@Override
public DynamoDbAsyncClient buildAsyncClient(final AwsCredentialsProvider awsCredentialsProvider) {
return EXTENSION.getDynamoDbAsyncClient();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.lettuce.core.resource.ClientResources;
import java.util.concurrent.atomic.AtomicBoolean;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@JsonTypeName("local")
public class LocalFaultTolerantRedisClusterFactory implements FaultTolerantRedisClusterFactory {
private static final RedisClusterExtension redisClusterExtension = RedisClusterExtension.builder().build();
private final AtomicBoolean shutdownHookConfigured = new AtomicBoolean();
private LocalFaultTolerantRedisClusterFactory() {
try {
redisClusterExtension.beforeAll(null);
redisClusterExtension.beforeEach(null);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@Override
public FaultTolerantRedisCluster build(final String name, final ClientResources.Builder clientResourcesBuilder) {
if (shutdownHookConfigured.compareAndSet(false, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
redisClusterExtension.afterEach(null);
redisClusterExtension.afterAll(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}));
}
final RedisClusterConfiguration config = new RedisClusterConfiguration();
config.setConfigurationUri(RedisClusterExtension.getRedisURIs().getFirst().toString());
return new FaultTolerantRedisCluster(name, config, clientResourcesBuilder);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.lifecycle.Managed;
import io.lettuce.core.RedisClient;
import io.lettuce.core.resource.ClientResources;
import java.util.concurrent.atomic.AtomicBoolean;
import org.whispersystems.textsecuregcm.redis.RedisSingletonExtension;
@JsonTypeName("local")
public class LocalSingletonRedisClientFactory implements SingletonRedisClientFactory, Managed {
private static final RedisSingletonExtension redisSingletonExtension = RedisSingletonExtension.builder().build();
private final AtomicBoolean shutdownHookConfigured = new AtomicBoolean();
private LocalSingletonRedisClientFactory() {
try {
redisSingletonExtension.beforeAll(null);
redisSingletonExtension.beforeEach(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public RedisClient build(final ClientResources clientResources) {
if (shutdownHookConfigured.compareAndSet(false, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
this.stop();
} catch (Exception e) {
throw new RuntimeException(e);
}
}));
}
return RedisClient.create(clientResources, redisSingletonExtension.getRedisUri());
}
@Override
public void stop() throws Exception {
redisSingletonExtension.afterEach(null);
redisSingletonExtension.afterAll(null);
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.time.Duration;
@JsonTypeName("nowait")
public class NoWaitDogstatsdConfiguration extends DogstatsdConfiguration {
@Override
public Duration getShutdownWaitDuration() {
return Duration.ZERO;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@JsonTypeName("static")
public class StaticDynamicConfigurationManagerFactory implements DynamicConfigurationManagerFactory {
@JsonProperty
@NotEmpty
private String application;
@JsonProperty
@NotEmpty
private String environment;
@JsonProperty
@NotEmpty
private String configuration;
@JsonProperty
@NotBlank
private String staticConfig;
@Override
public <T> DynamicConfigurationManager<T> build(final Class<T> klazz,
final ScheduledExecutorService scheduledExecutorService, final AwsCredentialsProvider awsCredentialsProvider) {
return new StaticDynamicConfigurationManager<>(staticConfig, application, environment, configuration,
awsCredentialsProvider, klazz, scheduledExecutorService);
}
private static class StaticDynamicConfigurationManager<T> extends DynamicConfigurationManager<T> {
private final T configuration;
public StaticDynamicConfigurationManager(final String config, final String application, final String environment,
final String configurationName, final AwsCredentialsProvider awsCredentialsProvider,
final Class<T> configurationClass, final ScheduledExecutorService scheduledExecutorService) {
super(application, environment, configurationName, awsCredentialsProvider, configurationClass,
scheduledExecutorService);
try {
this.configuration = parseConfiguration(config, configurationClass).orElseThrow();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
@Override
public T getConfiguration() {
return configuration;
}
@Override
public void start() {
// do nothing
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
@JsonTypeName("static")
public class StaticS3ObjectMonitorFactory implements S3ObjectMonitorFactory {
@JsonProperty
private byte[] object = new byte[0];
@Override
public S3ObjectMonitor build(final AwsCredentialsProvider awsCredentialsProvider,
final ScheduledExecutorService refreshExecutorService) {
return new StaticS3ObjectMonitor(object, awsCredentialsProvider);
}
private static class StaticS3ObjectMonitor extends S3ObjectMonitor {
private final byte[] object;
public StaticS3ObjectMonitor(final byte[] object, final AwsCredentialsProvider awsCredentialsProvider) {
super(awsCredentialsProvider, "local-test-region", "test-bucket", null, 0L, null, null);
this.object = object;
}
@Override
public synchronized void start(final Consumer<InputStream> changeListener) {
changeListener.accept(new ByteArrayInputStream(object));
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import org.whispersystems.textsecuregcm.captcha.Action;
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
@JsonTypeName("stub")
public class StubHCaptchaClientFactory implements HCaptchaClientFactory {
@Override
public HCaptchaClient build(final ScheduledExecutorService retryExecutor,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
return new StubHCaptchaClient(retryExecutor, new CircuitBreakerConfiguration(), dynamicConfigurationManager);
}
/**
* Accepts any token of the format "test.test.*.*"
*/
private static class StubHCaptchaClient extends HCaptchaClient {
public StubHCaptchaClient(final ScheduledExecutorService retryExecutor,
final CircuitBreakerConfiguration circuitBreakerConfiguration,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
super(null, retryExecutor, circuitBreakerConfiguration, null, dynamicConfigurationManager);
}
@Override
public String scheme() {
return "test";
}
@Override
public Set<String> validSiteKeys(final Action action) {
return Set.of("test");
}
@Override
public AssessmentResult verify(final String siteKey, final Action action, final String token, final String ip)
throws IOException {
return AssessmentResult.alwaysValid();
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.math.BigDecimal;
import java.net.http.HttpClient;
import java.util.Collections;
import java.util.Map;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
import org.whispersystems.textsecuregcm.currency.FixerClient;
@JsonTypeName("stub")
public class StubPaymentsServiceClientsFactory implements PaymentsServiceClientsFactory {
@Override
public FixerClient buildFixerClient(final HttpClient httpClient) {
return new StubFixerClient();
}
@Override
public CoinMarketCapClient buildCoinMarketCapClient(final HttpClient httpClient) {
return new StubCoinMarketCapClient();
}
/**
* Always returns an empty map of conversions
*/
private static class StubFixerClient extends FixerClient {
public StubFixerClient() {
super(null, null);
}
@Override
public Map<String, BigDecimal> getConversionsForBase(final String base) throws FixerException {
return Collections.emptyMap();
}
}
/**
* Always returns {@code 0} for spot price checks
*/
private static class StubCoinMarketCapClient extends CoinMarketCapClient {
public StubCoinMarketCapClient() {
super(null, null, null);
}
@Override
public BigDecimal getSpotPrice(final String currency, final String base) {
return BigDecimal.ZERO;
}
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.api.core.ApiFutures;
import com.google.cloud.pubsub.v1.PublisherInterface;
import java.util.UUID;
@JsonTypeName("stub")
public class StubPubSubPublisherFactory implements PubSubPublisherFactory {
@Override
public PublisherInterface build() {
return message -> ApiFutures.immediateFuture(UUID.randomUUID().toString());
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import io.dropwizard.core.setup.Environment;
import java.io.IOException;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
@JsonTypeName("stub")
public class StubRegistrationServiceClientFactory implements RegistrationServiceClientFactory {
@JsonProperty
@NotNull
private String registrationCaCertificate;
@Override
public RegistrationServiceClient build(final Environment environment, final Executor callbackExecutor,
final ScheduledExecutorService identityRefreshExecutor) {
try {
return new StubRegistrationServiceClient(registrationCaCertificate);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static class StubRegistrationServiceClient extends RegistrationServiceClient {
private final static Map<String, RegistrationServiceSession> SESSIONS = new ConcurrentHashMap<>();
public StubRegistrationServiceClient(final String registrationCaCertificate) throws IOException {
super("example.com", 8080, null, registrationCaCertificate, null);
}
@Override
public CompletableFuture<RegistrationServiceSession> createRegistrationSession(
final Phonenumber.PhoneNumber phoneNumber, final boolean accountExistsWithPhoneNumber, final Duration timeout) {
final String e164 = PhoneNumberUtil.getInstance()
.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
final byte[] id = new byte[32];
new SecureRandom().nextBytes(id);
final RegistrationServiceSession session = new RegistrationServiceSession(id, e164, false, 0L, 0L, null,
Instant.now().plus(Duration.ofMinutes(10)).toEpochMilli());
SESSIONS.put(Base64.getEncoder().encodeToString(id), session);
return CompletableFuture.completedFuture(session);
}
@Override
public CompletableFuture<RegistrationServiceSession> sendVerificationCode(final byte[] sessionId,
final MessageTransport messageTransport, final ClientType clientType, final @Nullable String acceptLanguage,
final @Nullable String senderOverride, final Duration timeout) {
return CompletableFuture.completedFuture(SESSIONS.get(Base64.getEncoder().encodeToString(sessionId)));
}
@Override
public CompletableFuture<RegistrationServiceSession> checkVerificationCode(final byte[] sessionId,
final String verificationCode, final Duration timeout) {
final RegistrationServiceSession session = SESSIONS.get(Base64.getEncoder().encodeToString(sessionId));
final RegistrationServiceSession updatedSession = new RegistrationServiceSession(sessionId, session.number(),
true, 0L, 0L, 0L,
Instant.now().plus(Duration.ofMinutes(10)).toEpochMilli());
SESSIONS.put(Base64.getEncoder().encodeToString(sessionId), updatedSession);
return CompletableFuture.completedFuture(updatedSession);
}
@Override
public CompletableFuture<Optional<RegistrationServiceSession>> getSession(final byte[] sessionId,
final Duration timeout) {
return CompletableFuture.completedFuture(
Optional.ofNullable(SESSIONS.get(Base64.getEncoder().encodeToString(sessionId))));
}
}
}

View File

@ -8,7 +8,6 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import java.time.Duration;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -32,7 +31,7 @@ class ProvisioningManagerTest {
@BeforeEach @BeforeEach
void setUp() throws Exception { void setUp() throws Exception {
provisioningManager = new ProvisioningManager(REDIS_EXTENSION.getRedisClient(), Duration.ofSeconds(1), new CircuitBreakerConfiguration()); provisioningManager = new ProvisioningManager(REDIS_EXTENSION.getRedisClient(), new CircuitBreakerConfiguration());
provisioningManager.start(); provisioningManager.start();
} }

View File

@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.redis;
import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeFalse;
import io.lettuce.core.RedisClient; import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.StatefulRedisConnection;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
@ -22,6 +23,7 @@ public class RedisSingletonExtension implements BeforeAllCallback, BeforeEachCal
private static RedisServer redisServer; private static RedisServer redisServer;
private RedisClient redisClient; private RedisClient redisClient;
private RedisURI redisUri;
public static class RedisSingletonExtensionBuilder { public static class RedisSingletonExtensionBuilder {
@ -53,7 +55,8 @@ public class RedisSingletonExtension implements BeforeAllCallback, BeforeEachCal
@Override @Override
public void beforeEach(final ExtensionContext context) { public void beforeEach(final ExtensionContext context) {
redisClient = RedisClient.create(String.format("redis://127.0.0.1:%d", redisServer.ports().get(0))); redisUri = RedisURI.create("redis://127.0.0.1:%d".formatted(redisServer.ports().get(0)));
redisClient = RedisClient.create(redisUri);
try (final StatefulRedisConnection<String, String> connection = redisClient.connect()) { try (final StatefulRedisConnection<String, String> connection = redisClient.connect()) {
connection.sync().flushall(); connection.sync().flushall();
@ -76,6 +79,10 @@ public class RedisSingletonExtension implements BeforeAllCallback, BeforeEachCal
return redisClient; return redisClient;
} }
public RedisURI getRedisUri() {
return redisUri;
}
private static int getAvailablePort() throws IOException { private static int getAvailablePort() throws IOException {
try (ServerSocket socket = new ServerSocket(0)) { try (ServerSocket socket = new ServerSocket(0)) {
socket.setReuseAddress(false); socket.setReuseAddress(false);

View File

@ -46,8 +46,7 @@ class S3ObjectMonitorTest {
objectKey, objectKey,
16 * 1024 * 1024, 16 * 1024 * 1024,
mock(ScheduledExecutorService.class), mock(ScheduledExecutorService.class),
Duration.ofMinutes(1), Duration.ofMinutes(1));
listener);
final String uuid = UUID.randomUUID().toString(); final String uuid = UUID.randomUUID().toString();
when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn( when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(
@ -55,8 +54,8 @@ class S3ObjectMonitorTest {
final ResponseInputStream<GetObjectResponse> ris = responseInputStreamFromString("abc", uuid); final ResponseInputStream<GetObjectResponse> ris = responseInputStreamFromString("abc", uuid);
when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris); when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris);
objectMonitor.refresh(); objectMonitor.refresh(listener);
objectMonitor.refresh(); objectMonitor.refresh(listener);
verify(listener).accept(ris); verify(listener).accept(ris);
} }
@ -77,8 +76,7 @@ class S3ObjectMonitorTest {
objectKey, objectKey,
16 * 1024 * 1024, 16 * 1024 * 1024,
mock(ScheduledExecutorService.class), mock(ScheduledExecutorService.class),
Duration.ofMinutes(1), Duration.ofMinutes(1));
listener);
final String uuid = UUID.randomUUID().toString(); final String uuid = UUID.randomUUID().toString();
when(s3Client.headObject(HeadObjectRequest.builder().key(objectKey).bucket(bucket).build())) when(s3Client.headObject(HeadObjectRequest.builder().key(objectKey).bucket(bucket).build()))
@ -87,7 +85,7 @@ class S3ObjectMonitorTest {
when(s3Client.getObject(GetObjectRequest.builder().key(objectKey).bucket(bucket).build())).thenReturn(responseInputStream); when(s3Client.getObject(GetObjectRequest.builder().key(objectKey).bucket(bucket).build())).thenReturn(responseInputStream);
objectMonitor.getObject(); objectMonitor.getObject();
objectMonitor.refresh(); objectMonitor.refresh(listener);
verify(listener, never()).accept(responseInputStream); verify(listener, never()).accept(responseInputStream);
} }
@ -115,8 +113,7 @@ class S3ObjectMonitorTest {
objectKey, objectKey,
maxObjectSize, maxObjectSize,
mock(ScheduledExecutorService.class), mock(ScheduledExecutorService.class),
Duration.ofMinutes(1), Duration.ofMinutes(1));
listener);
final String uuid = UUID.randomUUID().toString(); final String uuid = UUID.randomUUID().toString();
when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn( when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(
@ -124,7 +121,7 @@ class S3ObjectMonitorTest {
final ResponseInputStream<GetObjectResponse> ris = responseInputStreamFromString("a".repeat((int) maxObjectSize+1), uuid); final ResponseInputStream<GetObjectResponse> ris = responseInputStreamFromString("a".repeat((int) maxObjectSize+1), uuid);
when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris); when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris);
objectMonitor.refresh(); objectMonitor.refresh(listener);
verify(listener, never()).accept(any()); verify(listener, never()).accept(any());
} }

View File

@ -11,6 +11,7 @@ import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback;
@ -27,9 +28,12 @@ import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType; import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex; import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import javax.annotation.Nullable;
public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback { public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback {
private static final String DEFAULT_LIBRARY_PATH = "target/lib";
public interface TableSchema { public interface TableSchema {
String tableName(); String tableName();
String hashKeyName(); String hashKeyName();
@ -58,22 +62,28 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
private DynamoDBProxyServer server; private DynamoDBProxyServer server;
private int port; private int port;
private final String libraryPath;
private final List<TableSchema> schemas; private final List<TableSchema> schemas;
private DynamoDbClient dynamoDB2; private DynamoDbClient dynamoDB2;
private DynamoDbAsyncClient dynamoAsyncDB2; private DynamoDbAsyncClient dynamoAsyncDB2;
public DynamoDbExtension(TableSchema... schemas) { public DynamoDbExtension(TableSchema... schemas) {
this(DEFAULT_LIBRARY_PATH, schemas);
}
public DynamoDbExtension(@Nullable final String libraryPath, TableSchema... schemas) {
this.libraryPath = Optional.ofNullable(libraryPath).orElse(DEFAULT_LIBRARY_PATH);
this.schemas = List.of(schemas); this.schemas = List.of(schemas);
} }
private static void loadLibrary() { private void loadLibrary() {
// to avoid noise in the logs from library already loaded warnings, we make sure we only set it once // to avoid noise in the logs from library already loaded warnings, we make sure we only set it once
if (libraryLoaded.get()) { if (libraryLoaded.get()) {
return; return;
} }
if (libraryLoaded.compareAndSet(false, true)) { if (libraryLoaded.compareAndSet(false, true)) {
// if you see a library failed to load error, you need to run mvn test-compile at least once first // if you see a library failed to load error, you need to run mvn test-compile at least once first
SQLite.setLibraryPath("target/lib"); SQLite.setLibraryPath(this.libraryPath);
} }
} }

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.NoWaitDogstatsdConfiguration

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StaticDynamicConfigurationManagerFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.LocalDynamoDbFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.LocalFaultTolerantRedisClusterFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StubHCaptchaClientFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StubPaymentsServiceClientsFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StubPubSubPublisherFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StubRegistrationServiceClientFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.StaticS3ObjectMonitorFactory

View File

@ -0,0 +1 @@
org.whispersystems.textsecuregcm.configuration.LocalSingletonRedisClientFactory

View File

@ -0,0 +1,132 @@
aws.accessKeyId: accessKey
aws.secretAccessKey: secretAccess
stripe.apiKey: unset
stripe.idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
braintree.privateKey: unset
directoryV2.client.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
directoryV2.client.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
svr2.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users
svr2.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users
svr3.userAuthenticationTokenSharedSecret: cbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR3 to generate auth tokens for Signal users
svr3.userIdTokenSharedSecret: dbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR3 to generate auth identity tokens for Signal users
tus.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG=
awsAttachments.accessKey: test
awsAttachments.accessSecret: test
# The below private key was key generated exclusively for testing purposes. Do not use it in any other context.
gcpAttachments.rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrfHLw9zr/8mTX
c0YMN3P9pNLtn+JCsNx/6sz/7FYoJjH8CKG4zNgcJLATLGxQikTjD6yNDlgkpByD
qOmgXgZvIBBJadbbl+plJbU4kKwTRwdrYiq/ICMkVZBk5jfqYqSxzdw80ytj5Tha
3M/3uqto7qELK91z/5cCC6pVsQXIrTqq4D41XyORKF2u4eeKOz3jiuXkdxRj4Vsb
MDwcS1WEi1ApoG50tDDn7e9mk3MAeE5L54ROHkd7FM471LRSU9ytpOzcH56tExLP
21nN5vXZoyJnNvbgd1KZeZajjH+XHJS/wiqNAPEX2yvrFID4ECQMIonXtYyNDkmY
YxggNaCnAgMBAAECggEAFLDJStr+8A7BArXSh9AmWz4zLPSTiim+EQ5gJFN8Tw/S
DBob2SjuEkc4RLf2waj33XrwqNGdlPOFdTqWJavylB8xl99V9dzYgn0QO9OeJMf3
Kd+y+f3Yqkj188FLPH52Z0ryqGwaL3gNWqPge9VhWncgUIa/C4CVKcFakJ2b7bW2
NIk2bSMCNW8rptQZ+tWV9k86OAxjIocLbkpPgigRk6T3MAunMGVf6iviNSnOyOlZ
qmAPkRVs2uyK3Hnl0lEavaBW3KRs0ChU0rkfXHvGmi7V6aZ4rnG6OdRQiOgk3NYf
qQYqhnRMmN4st2WN6CDDdpk5o2pHR625Wqx11t/50QKBgQDmf+fYWKdQa8r+TO4w
32JAiEdmFuA8fSEOaWyBik/NliJIPEApGMWLuZSmSzW80l4vt5zQ3LVgvRrxZv2y
7odLxUP9jpFGVg3NpCB27nES+psmo7X4kXIfzPWGvkOs2HLpp8elVEPeOn7gkng9
XXXmB9vja8g/Jo9ym9FkigB0LQKBgQC+dTFTPvvVYFQ1KmeL94EOEL21ZXkgwjnx
1BcnqK4p0M1NQ2xW1wwCljxlEQx5P6UY9HRWS6DecVpj6P7nRF2HWB+xsaO1aPZj
nMOETrUXGq8ksQml+0kI5f0A2w22wzpj3+kjiXSFBjxoWLAfKPHMKeUg/oYRfIVp
LeShMptIowKBgQC4H44U3ORyMlkKAGv4sEhs4i+elkFzMEU6nO4nIFQVFou2BiL+
cSJENe9PUx7PAYBpP5PNp7BfYU/na+zWhQGgfiiMn9jeRZlrHmMsfdXnYjaTjAyt
TYnLa07p3oxywsgwa2zoXUKFf1agj3/rDQBDyx1UMmHYSDYoR93hIPex1QKBgQCF
4y6sna89ff1Ubp3iKDjiIWSre00eeUtwtC8e4xakMLPSZ95mYcCApQqJ5eVF6zbt
hxOtgnbxSPBJIgbnnwi813dYXE+AfOwQdKiBfy8QseKDwazNsQvTpJIqItPOMgn/
Ie3r3Ho79XlLxWTyUr9ATgdUHXk0G7xRh0CdDU1aTwKBgC5kDNr/R2XIWZL0TMzz
EVL2BkL11YumIpEBm+Hkx6fm3uCgR/ywMqplGdZcD+D5r0fUsckbOd1z6fFGAJqe
QJ3/4qaA+dcWPwB5GiKa1WIs48GJMyPrFciindEwr3BaDhhB9cEdxpVY2e/KEeZL
TQkqmVUmgKKvCFTPWwCgeIOD
-----END PRIVATE KEY-----
apn.teamId: team-id
apn.keyId: key-id
# The below private key was key generated exclusively for testing purposes. Do not use it in any other context.
apn.signingKey: |
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxIXnNiHH35DDbKHY
8kxoAYbukvMPVWN+kiIhZsFvqaahRANCAAQTWXjgagaLnTxcMJTUpO3rkhi8xjav
7NSEd5L+df4M7V9YxxDoYY+UHd8B/KmrWR29SVIRLncSULgfSnHnHvoH
-----END PRIVATE KEY-----
# The below private key was key generated exclusively for testing purposes. Do not use it in any other context.
fcm.credentials: |
{ "type": "service_account", "client_id": "client_id", "client_email": "fake@example.com",
"private_key_id": "id",
"private_key": "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrfHLw9zr/8mTX
c0YMN3P9pNLtn+JCsNx/6sz/7FYoJjH8CKG4zNgcJLATLGxQikTjD6yNDlgkpByD
qOmgXgZvIBBJadbbl+plJbU4kKwTRwdrYiq/ICMkVZBk5jfqYqSxzdw80ytj5Tha
3M/3uqto7qELK91z/5cCC6pVsQXIrTqq4D41XyORKF2u4eeKOz3jiuXkdxRj4Vsb
MDwcS1WEi1ApoG50tDDn7e9mk3MAeE5L54ROHkd7FM471LRSU9ytpOzcH56tExLP
21nN5vXZoyJnNvbgd1KZeZajjH+XHJS/wiqNAPEX2yvrFID4ECQMIonXtYyNDkmY
YxggNaCnAgMBAAECggEAFLDJStr+8A7BArXSh9AmWz4zLPSTiim+EQ5gJFN8Tw/S
DBob2SjuEkc4RLf2waj33XrwqNGdlPOFdTqWJavylB8xl99V9dzYgn0QO9OeJMf3
Kd+y+f3Yqkj188FLPH52Z0ryqGwaL3gNWqPge9VhWncgUIa/C4CVKcFakJ2b7bW2
NIk2bSMCNW8rptQZ+tWV9k86OAxjIocLbkpPgigRk6T3MAunMGVf6iviNSnOyOlZ
qmAPkRVs2uyK3Hnl0lEavaBW3KRs0ChU0rkfXHvGmi7V6aZ4rnG6OdRQiOgk3NYf
qQYqhnRMmN4st2WN6CDDdpk5o2pHR625Wqx11t/50QKBgQDmf+fYWKdQa8r+TO4w
32JAiEdmFuA8fSEOaWyBik/NliJIPEApGMWLuZSmSzW80l4vt5zQ3LVgvRrxZv2y
7odLxUP9jpFGVg3NpCB27nES+psmo7X4kXIfzPWGvkOs2HLpp8elVEPeOn7gkng9
XXXmB9vja8g/Jo9ym9FkigB0LQKBgQC+dTFTPvvVYFQ1KmeL94EOEL21ZXkgwjnx
1BcnqK4p0M1NQ2xW1wwCljxlEQx5P6UY9HRWS6DecVpj6P7nRF2HWB+xsaO1aPZj
nMOETrUXGq8ksQml+0kI5f0A2w22wzpj3+kjiXSFBjxoWLAfKPHMKeUg/oYRfIVp
LeShMptIowKBgQC4H44U3ORyMlkKAGv4sEhs4i+elkFzMEU6nO4nIFQVFou2BiL+
cSJENe9PUx7PAYBpP5PNp7BfYU/na+zWhQGgfiiMn9jeRZlrHmMsfdXnYjaTjAyt
TYnLa07p3oxywsgwa2zoXUKFf1agj3/rDQBDyx1UMmHYSDYoR93hIPex1QKBgQCF
4y6sna89ff1Ubp3iKDjiIWSre00eeUtwtC8e4xakMLPSZ95mYcCApQqJ5eVF6zbt
hxOtgnbxSPBJIgbnnwi813dYXE+AfOwQdKiBfy8QseKDwazNsQvTpJIqItPOMgn/
Ie3r3Ho79XlLxWTyUr9ATgdUHXk0G7xRh0CdDU1aTwKBgC5kDNr/R2XIWZL0TMzz
EVL2BkL11YumIpEBm+Hkx6fm3uCgR/ywMqplGdZcD+D5r0fUsckbOd1z6fFGAJqe
QJ3/4qaA+dcWPwB5GiKa1WIs48GJMyPrFciindEwr3BaDhhB9cEdxpVY2e/KEeZL
TQkqmVUmgKKvCFTPWwCgeIOD
-----END PRIVATE KEY-----" }
cdn.accessKey: test # AWS Access Key ID
cdn.accessSecret: test # AWS Access Secret
cdn3StorageManager.clientSecret: test
# The below private key was key generated exclusively for testing purposes. Do not use it in any other context.
# ca:
# Public key : BVDuaR1ZT/5M26nSvFN1XjN4qFqVfwynt03l/GyK2GtP
# Private key: 0Ie2/CIxfidwhS+uKSckb2YgtRR7UBLkecHNG2ARiW8=
unidentifiedDelivery.certificate: CikIp/SIvwESIQWXK5ikKQdvB7XpwPowV4djRj6Ni0I9MO12AVNpzQ9DDBJApovqX/gmIc+ukn4YeYtCsk9Q0EoDoXAZPD0D5vflcnncUs2fFLEQLlnSZ/ckXVChZByyuiNiegtl468+A+u5gw==
unidentifiedDelivery.privateKey: UKRdvrnxcy1hcILN7RHqUG40/mUd/ZnKkqhQToN4K1U=
storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
# The below secrets were generated exclusively for testing purposes. Do not use them in any other context.
zkConfig-libsignal-0.42.serverSecret: ALxy0qUiV9B3OgF1GzTgn6g4NSN22ww87p5xOlYkypQJIqxoTBGOPREr4ZXvOfQ/tFYQJxn3xoFJ5DKt+mDOJw8KzWephNaaXBSLcbWb/fPfSUjoXnPL+fcU26OpMIUrIVews/i0Eh4ongRWuLfFUpLyZ34wfgpKkWbIUBjbVT4M5e7GzRPVFOBGibICk9Q66o9l3K4PphKaoMQKlGPo4g85xsVH1HFN5J5u4/pdq80+3E7WvWN4c8NpDZQ7RqtECDpawSc/J4vim9tL7QR6hFYwzvA0cmCcM0NCPFpp69EHB1eRksDvPHiA+NuMqoFwXwIzSHzA86ALPxnyLKB7KgBG06DxfMmMav3dij6giTawgATRsoNFLQ/ol6H0TtYJCAp8oB0D4EV2q7hSue3Kxzh1Vc88/nmLuRR9G3EefC0+CMcxJFQwDMgjFvFBKx3o6m9gJLevYiKcm/NxXX9WtnEzqAh2DRr0G0fvk9NZF2Lw2kWgAX2qkPHZLJ3nKA90BgryBAJsk8Q78N5ghBhQzdgikURLC+mX1fbmMzkmGcwCYDnLpo8qjrIoBZzDAjI4Ty04MaJcLowJqNMmK29btza50WiZdsd4tuVqQAKlqJcERCsUewlZSkWpsDLrZkeUBY0rGCi51FW5WOUvdXwTHtTL2hlcBqP/E8cbBC+yce8AurjJ6Z9HVtM7tVk7a5xRAqwoFRSH7eq5BA60hDq2sgWIQaN1owunKZvsHFn0qzoGKuWAEO0PpbGbHtFDUjxzBkgUIN13yIbem9KPeZm9ZVrjkyDo3uZZsfFFUHnFeasKOt9WMJLLx1s7DttJ4Ns2o02q4e+aQ60oMeWsyMuzKgNMDHgDgfqHxbCi2rm20SgoHnuoph6XArmEOX6a1xLJVxgDtgfm1IbcyyqROXYxe9v2RvMUAnjbLI/fm0rXXhldjVX7Yoo7ZGAlVBuGOC28haRrd684Lcajdequ6Css1geOzOVVH/BGnJtf3RFSMXIv/YByG21/cgL9KFYqQHqZZeLNQpweILMnU0/iVK/fjLvrhyI2jNy1B0Ox62zE2o8EdVT/H1WgXa2NHC591aEqI5EXwribRKUM56v/O3IDBAxC5CLIQcUeDhWouaFqXjfxNza9rFC69smtUXl3sx8KG6Ze7VkXb372daAN+rMWIdq5kbRcetkXxuqTuYOz4NsDnEPnBNNO6hqOv6+dZAQK3wmhOah3NIL0kKNUHpuk+gidQkLehBvahNKpUfh9yBMkEcLNkR7D1r8fAcrA7u2sCKfRero8FOkCc9ChawShJXdcGtKv50d2/4vJp9B0ddUBYdFnbM/siX4VJghb+rSkhCkkXXS7QXFfbO5A4WJLkwthkNezNgqCBmEoda7UcOOaW9KQMFisRy74OZagCUAJCPC4UJw1/N4IJD7Dtw2cNtaxwFyuG6i7sdm9u3Xr9h0JcvKn5Z3BLxmR6WkhO/IraGw+9/ijFpEtB9VFhdQxfnnj4JRneZtQxC9nAY0liDuO41OhGYaWinVVRkljKe7GAmw726P1wYiyd2ajlkbI/7KB9VwxEicEijeyuR6UZDUFqOPk4q+fdkBRS2GuGKiy4ISgha6sVRkb5sLlAsRhmG1W0Anh8d2dHxmE8CzyoAXbschIeY2LJTUIORpvAaFrDI74dagBDNlIVnHw89PGYWnosia9YFMLpsYyjOccQKAGKJLZ8MdcgRw8W2Mw2AwZbQWUUU1VZxdWX6y878WJ6g4at5NJ2YpdZ/qh5zBTDbTDpGnTsv9Ioyw5+91h6qagDXecb1wekN0RZsI3KWXQeOZZ+uxTNRqqpWZhH9QtrEsPRdNyOlVJDmJ1H2Rl3o30Crt8j6EM6ZgR444GuDf/jATe9shz3Ljc0S5/OTEAJL54OfHq9jGgtXS+05AuhlxCC9zYpEBb30xnZD6zxXnz6IB//uVROuWc1BEFhkvn+JcJxpapbKH6PthuOzMVRkf+I3Xz3/bNjiQSlQkmAXlgB1YujgABYnJ6yJXQKP2mR4UJ3UYoGroYoafWycDa+vUYYqIMcK6tbIgvxFx8TmMoQ1MueOIzDt0Nyx3Uov5qVvcG8gyflIv4fbzlu7GTYE8Ov6sRGY8KzF5ywxvrq0VldgfoGF4AGdQ3RxB5EDdlHIvlOG8VRoD+7Ch/S1kemdyvD2co9wN/GfU58Q1DO7dSQTl/O86t1eZBp+8H4IIarAgLunN/vkV34LAYjk1DOfeNxrCnfHz9RWtT3Vy3FJKuaQUotgZRyu36BLSs/ozriMR1nkT2+Luknw/zDD496ZvyAwvtG18Tk5B5b3DSMSUq9vA4h8KKCFfkgTHNeOHFyogNkfwaeGEflkrckI4RTtGrIL+lmW+1LYRoDU8F2T4VzAnyqsfmfT5+g6nbzw7FRXGfEu/E94Xaacj0t90t+eqtBruaoJ9kqw4iJmQdrSonz4fo4yOAXukaTnzFnalXZdoFjNpSbMWOwFhalgT4fI8mUnZBoVOulWrFfs1exJuDy3fki99Z9kwT1gFnZ4SsO6fCRoTiINCEeXkhcCFscOuvZP9su7zhyXWhobzNsct/ejd+CHyDKoNPBjNId5hGwnIAP5F55x+n51UIPvCkotwIGsvbfExLMw/JgwCJCDSHNgZEmXO+xEVozKRbUDK8d0mR46M59k8qaPKkKAORatQPVXOyszQTLx8gnPf/HDDqthyyp7mfjfE09vv3CpREfzkGYZpMWv1aDG2AHpAdrOH3cVW2UQq9uPRtkiHZMZg9CQnapTCmq3YAvsKugvU2CnrnJlACFYO1Hr5RpjfIRMCkBrfHrdFQEwB4/u6opMApJThcbXzhbVEwIwOq/ZcleloJKnN4GdaZyLFphtApVSuMVYDNm0X6KNGSl1kFbgxs8gBBhLxXqqhdfoBnwOtOXHO+kFGY1WzGUJZviHD43glxBIWPRSjD0pPvuWX91IWv/GZuCIkwCa34p+P9v7iKRuoBGievGCJJ+5SUoBQyhhQdpG+gbw1Bs3KSPwkx0IulpQqkInUCyVgxRXZWI61AkDNr7ybYMhq0nPPY3V0xYzN9Wltf8c37m3IWVkzGR3a0JjoUUWtFaCY9fasiOQM1nZUMe/0UTV2ZKjD4US9c2PEHrPPlwVowar4PlUJU1KIAjgsCyo9DhZPyEgPjGYu+tGpMcUImLukfebXsgzFFhyLgLktlMwFHNn6JHchCURY58OOcMzDKK/6IuLU621a+d8gP8U22MozRrL+GNEYwiyF9XM4Hp+ovB3yv6VFkBp7ukAJDETnNy+nPPjt0ShUpp4hj+WDWo/fs6Oy/fs0wPdziBPrWQ7Dn0vDGsXVbTib8rd4UucpZdGaY1yktsG4MNHAMVv+IH1hYcg87bfsUJbfuHgvLB1l5Qz8j7/Ezi54RFQBGS3QkDQnCl/mMmrNCe5xe1soC+rsCRblHuJjujjK/CxgYEs2Lc9ZWPc3FyzGQbblH5hUX1MxP0V1DM/VxpI8EGVWTk6Q3W1yX16EiWkauVbHsyScbniotURYRstCUmA1Qnz9bsSBgjuCftVHZZ4lFmWogd976tG5uGQ+tvu6xCqH+EsGOQ843I/5w0xTPTJcFyQ9cuRoTPzFIeP2wa9AA
genericZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
callingZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
backupsZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
paymentsService.fixerApiKey: unset
paymentsService.coinMarketCapApiKey: unset
artService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret not shared with any external service, but used in ArtController
artService.userAuthenticationTokenUserIdSecret: AAAAAAAAAAA= # base64-encoded secret to obscure user phone numbers from Sticker Creator
currentReportingKey.secret: AAAAAAAAAAA=
currentReportingKey.salt: AAAAAAAAAAA=
turn.secret: AAAAAAAAAAA=
linkDevice.secret: AAAAAAAAAAA=
tlsKeyStore.password: unset
noiseTunnel.recognizedProxySecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA

View File

@ -0,0 +1,472 @@
logging:
level: INFO
appenders:
- type: console
threshold: ALL
timeZone: UTC
target: stdout
health:
delayedShutdownHandlerEnabled: false
awsCredentialsProvider:
type: static
accessKeyId: secret://aws.accessKeyId
secretAccessKey: secret://aws.secretAccessKey
metrics:
reporters:
- type: signal-datadog
frequency: 10 seconds
tags:
- "env:test"
- "service:chat"
udpTransport:
statsdHost: localhost
port: 8125
excludesAttributes:
- m1_rate
- m5_rate
- m15_rate
- mean_rate
- stddev
useRegexFilters: true
excludes:
- ^.+\.total$
- ^.+\.request\.filtering$
- ^.+\.response\.filtering$
- ^executor\..+$
- ^lettuce\..+$
reportOnStop: true
tlsKeyStore:
password: secret://tlsKeyStore.password
stripe:
apiKey: secret://stripe.apiKey
idempotencyKeyGenerator: secret://stripe.idempotencyKeyGenerator
boostDescription: >
Example
supportedCurrenciesByPaymentMethod:
CARD:
- usd
- eur
SEPA_DEBIT:
- eur
braintree:
merchantId: unset
publicKey: unset
privateKey: secret://braintree.privateKey
environment: sandbox
graphqlUrl: unset
merchantAccounts:
# ISO 4217 currency code and its corresponding sub-merchant account
'xts': unset
supportedCurrenciesByPaymentMethod:
PAYPAL:
- usd
pubSubPublisher:
type: stub
dynamoDbClient:
type: local
dynamoDbTables:
accounts:
tableName: accounts_test
phoneNumberTableName: numbers_test
phoneNumberIdentifierTableName: pni_assignment_test
usernamesTableName: usernames_test
backups:
tableName: backups_test
clientReleases:
tableName: client_releases_test
deletedAccounts:
tableName: deleted_accounts_test
deletedAccountsLock:
tableName: deleted_accounts_lock_test
issuedReceipts:
tableName: issued_receipts_test
expiration: P30D # Duration of time until rows expire
generator: abcdefg12345678= # random base64-encoded binary sequence
ecKeys:
tableName: keys_test
ecSignedPreKeys:
tableName: repeated_use_signed_ec_pre_keys_test
pqKeys:
tableName: pq_keys_test
pqLastResortKeys:
tableName: repeated_use_signed_kem_pre_keys_test
messages:
tableName: messages_test
expiration: P30D # Duration of time until rows expire
onetimeDonations:
tableName: onetime_donations_test
expiration: P90D
phoneNumberIdentifiers:
tableName: pni_test
profiles:
tableName: profiles_test
pushChallenge:
tableName: push_challenge_test
redeemedReceipts:
tableName: redeemed_receipts_test
expiration: P30D # Duration of time until rows expire
registrationRecovery:
tableName: registration_recovery_passwords_test
expiration: P300D # Duration of time until rows expire
remoteConfig:
tableName: remote_config_test
reportMessage:
tableName: report_messages_test
subscriptions:
tableName: subscriptions_test
clientPublicKeys:
tableName: client_public_keys_test
verificationSessions:
tableName: verification_sessions_test
cacheCluster: # Redis server configuration for cache cluster
type: local
clientPresenceCluster: # Redis server configuration for client presence cluster
type: local
provisioning:
pubsub: # Redis server configuration for pubsub cluster
type: local
pushSchedulerCluster: # Redis server configuration for push scheduler cluster
type: local
rateLimitersCluster: # Redis server configuration for rate limiters cluster
type: local
directoryV2:
client: # Configuration for interfacing with Contact Discovery Service v2 cluster
userAuthenticationTokenSharedSecret: secret://directoryV2.client.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://directoryV2.client.userIdTokenSharedSecret
svr2:
uri: svr2.example.com
userAuthenticationTokenSharedSecret: secret://svr2.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://svr2.userIdTokenSharedSecret
svrCaCertificates:
# this is a randomly generated test certificate
- |
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUW5lcNWkuynRVc8Rq5pO6mHQBuZAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAzMjUwMzE4MTNaFw0yOTAz
MjQwMzE4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCfH4Um+fv2r4KudhD37/UXp8duRLTmp4XvpBTpDHpD
2HF8p2yThVKlJnMkP/9Ey1Rb0vhxO7DCltLdW8IYcxJuHoyMvyhGUEtxxkOZbrk8
ciUR9jTZ37x7vXRGj/RxcdlS6iD0MeF0D/LAkImt4T/kiKwDbENrVEnYWJmipCKP
ribxWky7HqxDCoYMQr0zatxB3A9mx5stH+H3kbw3CZcm+ugF9ZIKDEVHb0lf28gq
llmD120q/vs9YV3rzVL7sBGDqf6olkulvHQJKElZg2rdcHWFcngSlU2BjR04oyuH
c/SSiLSB3YB0tdFGta5uorXyV1y7RElPeBfOfvEjsG3TAgMBAAGjUzBRMB0GA1Ud
DgQWBBQX+xlgSWWbDjv0SrJ+h67xauJ80zAfBgNVHSMEGDAWgBQX+xlgSWWbDjv0
SrJ+h67xauJ80zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw
ZG2MCCjscn6h/QOoJU+IDfa68OqLq0I37gMnLMde4yEhAmm//miePIq4Uz9GRJ+h
rAmdEnspKgyQ93PjF7Xpk/JdJA4B1bIrsOl/cSwqx2sFhRt8Kt1DHGlGWXqOaHRP
UkZ86MyRL3sXly6WkxEYxZJeQaOzMy2XmQh7grzrlTBuSI+0xf7vsRRDipxr6LVQ
6qGWyGODLLc2JD1IXj/1HpRVT2LoGGlKMuyxACQAm4oak1vvJ9mGxgfd9AU+eo58
O/esB2Eaf+QqMPELdFSZQfG2jvp+3WQTZK8fDKHyLr076G3UetEMy867F6fzTSZd
9Kxq0DY7RCEpdHMCKcOL
-----END CERTIFICATE-----
svr3:
uri: svr3.example.com
userAuthenticationTokenSharedSecret: secret://svr3.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://svr3.userIdTokenSharedSecret
svrCaCertificates:
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
messageCache: # Redis server configuration for message store cache
persistDelayMinutes: 1
cluster:
type: local
metricsCluster:
type: local
awsAttachments: # AWS S3 configuration
bucket: aws-attachments
credentials:
accessKeyId: secret://awsAttachments.accessKey
secretAccessKey: secret://awsAttachments.accessSecret
region: us-west-2
gcpAttachments: # GCP Storage configuration
domain: example.com
email: user@example.cocm
maxSizeInBytes: 1024
pathPrefix:
rsaSigningKey: secret://gcpAttachments.rsaSigningKey
tus:
uploadUri: https://example.org/upload
userAuthenticationTokenSharedSecret: secret://tus.userAuthenticationTokenSharedSecret
apn: # Apple Push Notifications configuration
sandbox: true
bundleId: com.example.textsecuregcm
keyId: secret://apn.keyId
teamId: secret://apn.teamId
signingKey: secret://apn.signingKey
fcm: # FCM configuration
credentials: secret://fcm.credentials
cdn:
bucket: cdn # S3 Bucket name
credentials:
accessKeyId: secret://cdn.accessKey
secretAccessKey: secret://cdn.accessSecret
region: us-west-2 # AWS region
clientCdn:
attachmentUrls:
2: https://cdn2.example.com/attachments/
caCertificates:
- |
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUW5lcNWkuynRVc8Rq5pO6mHQBuZAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAzMjUwMzE4MTNaFw0yOTAz
MjQwMzE4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCfH4Um+fv2r4KudhD37/UXp8duRLTmp4XvpBTpDHpD
2HF8p2yThVKlJnMkP/9Ey1Rb0vhxO7DCltLdW8IYcxJuHoyMvyhGUEtxxkOZbrk8
ciUR9jTZ37x7vXRGj/RxcdlS6iD0MeF0D/LAkImt4T/kiKwDbENrVEnYWJmipCKP
ribxWky7HqxDCoYMQr0zatxB3A9mx5stH+H3kbw3CZcm+ugF9ZIKDEVHb0lf28gq
llmD120q/vs9YV3rzVL7sBGDqf6olkulvHQJKElZg2rdcHWFcngSlU2BjR04oyuH
c/SSiLSB3YB0tdFGta5uorXyV1y7RElPeBfOfvEjsG3TAgMBAAGjUzBRMB0GA1Ud
DgQWBBQX+xlgSWWbDjv0SrJ+h67xauJ80zAfBgNVHSMEGDAWgBQX+xlgSWWbDjv0
SrJ+h67xauJ80zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw
ZG2MCCjscn6h/QOoJU+IDfa68OqLq0I37gMnLMde4yEhAmm//miePIq4Uz9GRJ+h
rAmdEnspKgyQ93PjF7Xpk/JdJA4B1bIrsOl/cSwqx2sFhRt8Kt1DHGlGWXqOaHRP
UkZ86MyRL3sXly6WkxEYxZJeQaOzMy2XmQh7grzrlTBuSI+0xf7vsRRDipxr6LVQ
6qGWyGODLLc2JD1IXj/1HpRVT2LoGGlKMuyxACQAm4oak1vvJ9mGxgfd9AU+eo58
O/esB2Eaf+QqMPELdFSZQfG2jvp+3WQTZK8fDKHyLr076G3UetEMy867F6fzTSZd
9Kxq0DY7RCEpdHMCKcOL
-----END CERTIFICATE-----
cdn3StorageManager:
baseUri: https://storage-manager.example.com
clientId: example
clientSecret: secret://cdn3StorageManager.clientSecret
dogstatsd:
type: nowait
environment: dev
host: 127.0.0.1
unidentifiedDelivery:
certificate: secret://unidentifiedDelivery.certificate
privateKey: secret://unidentifiedDelivery.privateKey
expiresDays: 7
hCaptcha:
type: stub
shortCode:
baseUrl: https://example.com/shortcodes/
storageService:
uri: storage.example.com
userAuthenticationTokenSharedSecret: secret://storageService.userAuthenticationTokenSharedSecret
storageCaCertificates:
- |
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUW5lcNWkuynRVc8Rq5pO6mHQBuZAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAzMjUwMzE4MTNaFw0yOTAz
MjQwMzE4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCfH4Um+fv2r4KudhD37/UXp8duRLTmp4XvpBTpDHpD
2HF8p2yThVKlJnMkP/9Ey1Rb0vhxO7DCltLdW8IYcxJuHoyMvyhGUEtxxkOZbrk8
ciUR9jTZ37x7vXRGj/RxcdlS6iD0MeF0D/LAkImt4T/kiKwDbENrVEnYWJmipCKP
ribxWky7HqxDCoYMQr0zatxB3A9mx5stH+H3kbw3CZcm+ugF9ZIKDEVHb0lf28gq
llmD120q/vs9YV3rzVL7sBGDqf6olkulvHQJKElZg2rdcHWFcngSlU2BjR04oyuH
c/SSiLSB3YB0tdFGta5uorXyV1y7RElPeBfOfvEjsG3TAgMBAAGjUzBRMB0GA1Ud
DgQWBBQX+xlgSWWbDjv0SrJ+h67xauJ80zAfBgNVHSMEGDAWgBQX+xlgSWWbDjv0
SrJ+h67xauJ80zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw
ZG2MCCjscn6h/QOoJU+IDfa68OqLq0I37gMnLMde4yEhAmm//miePIq4Uz9GRJ+h
rAmdEnspKgyQ93PjF7Xpk/JdJA4B1bIrsOl/cSwqx2sFhRt8Kt1DHGlGWXqOaHRP
UkZ86MyRL3sXly6WkxEYxZJeQaOzMy2XmQh7grzrlTBuSI+0xf7vsRRDipxr6LVQ
6qGWyGODLLc2JD1IXj/1HpRVT2LoGGlKMuyxACQAm4oak1vvJ9mGxgfd9AU+eo58
O/esB2Eaf+QqMPELdFSZQfG2jvp+3WQTZK8fDKHyLr076G3UetEMy867F6fzTSZd
9Kxq0DY7RCEpdHMCKcOL
-----END CERTIFICATE-----
zkConfig:
serverPublic: AAp8oB0D4EV2q7hSue3Kxzh1Vc88/nmLuRR9G3EefC0+CMcxJFQwDMgjFvFBKx3o6m9gJLevYiKcm/NxXX9WtnFMDHgDgfqHxbCi2rm20SgoHnuoph6XArmEOX6a1xLJVxgDtgfm1IbcyyqROXYxe9v2RvMUAnjbLI/fm0rXXhldjszlVR/wRpybX90RUjFyL/2Achttf3IC/ShWKkB6mWXwuFCcNfzeCCQ+w7cNnDbWscBcrhuou7HZvbt16/YdCXLyp+WdwS8ZkelpITvyK2hsPvf4oxaRLQfVRYXUMX55xpapbKH6PthuOzMVRkf+I3Xz3/bNjiQSlQkmAXlgB1YujgABYnJ6yJXQKP2mR4UJ3UYoGroYoafWycDa+vUYYozaUmzFjsBYWpYE+HyPJlJ2QaFTrpVqxX7NXsSbg8t35IvfWfZME9YBZ2eErDunwkaE4iDQhHl5IXAhbHDrr2QaJ68YIkn7lJSgFDKGFB2kb6BvDUGzcpI/CTHQi6WlCqQidQLJWDFFdlYjrUCQM2vvJtgyGrSc89jdXTFjM31aqmtcPWgWL0qv+RmK/BC392Nsu8WoSJcAE4yhccQuRSemtolgwewnjasoOFBNOPh4+pX55SwhyTVgtwl+NTNVNFydxGp9Me8ogRWElzwA9BFtNAgQtlfgIyZRTetFqLkYmIBDxwMcpizDKES5lPhV2uJJuzcMq/06mVQz2OrXgglWk01uN8U59pfNFpTZhcGQv+MHjwEAudq5eLpt3aFrdxJ7D26Fwl5j215SJ0yZo7vmSEML1vf7FaGh0IL57bRpCvdebB5WapSChUX+PPvCXohVjGrERFvQpeET6pydGGlEKYLWuWa3zFGmPvJJYZ/QfcmIP9zyhqzQT/7a7RIqFA==
serverSecret: secret://zkConfig-libsignal-0.42.serverSecret
callingZkConfig:
serverSecret: secret://callingZkConfig.serverSecret
backupsZkConfig:
serverSecret: secret://backupsZkConfig.serverSecret
appConfig:
type: static
application: test
environment: test
configuration: test
staticConfig: |
captcha:
scoreFloor: 1.0
remoteConfig:
globalConfig: # keys and values that are given to clients on GET /v1/config
EXAMPLE_KEY: VALUE
paymentsService:
userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret
paymentCurrencies:
# list of symbols for supported currencies
- MOB
externalClients:
type: stub
artService:
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
badges:
badges:
- id: TEST
category: other
sprites: # exactly 6
- sprite-1.png
- sprite-2.png
- sprite-3.png
- sprite-4.png
- sprite-5.png
- sprite-6.png
svg: example.svg
svgs:
- light: example-light.svg
dark: example-dark.svg
badgeIdsEnabledForAll:
- TEST
receiptLevels:
'1': TEST
subscription: # configuration for Stripe subscriptions
badgeExpiration: P30D
badgeGracePeriod: P15D
levels:
500:
badge: EXAMPLE
prices:
# list of ISO 4217 currency codes and amounts for the given badge level
xts:
amount: '10'
processorIds:
STRIPE: price_example # stripe Price ID
BRAINTREE: plan_example # braintree Plan ID
oneTimeDonations:
sepaMaximumEuros: '10000'
boost:
level: 1
expiration: P90D
badge: EXAMPLE
gift:
level: 10
expiration: P90D
badge: EXAMPLE
currencies:
# ISO 4217 currency codes and amounts in those currencies
xts:
minimum: '0.5'
gift: '2'
boosts:
- '1'
- '2'
- '4'
- '8'
- '20'
- '40'
registrationService:
type: stub
registrationCaCertificate: |
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUW5lcNWkuynRVc8Rq5pO6mHQBuZAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAzMjUwMzE4MTNaFw0yOTAz
MjQwMzE4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCfH4Um+fv2r4KudhD37/UXp8duRLTmp4XvpBTpDHpD
2HF8p2yThVKlJnMkP/9Ey1Rb0vhxO7DCltLdW8IYcxJuHoyMvyhGUEtxxkOZbrk8
ciUR9jTZ37x7vXRGj/RxcdlS6iD0MeF0D/LAkImt4T/kiKwDbENrVEnYWJmipCKP
ribxWky7HqxDCoYMQr0zatxB3A9mx5stH+H3kbw3CZcm+ugF9ZIKDEVHb0lf28gq
llmD120q/vs9YV3rzVL7sBGDqf6olkulvHQJKElZg2rdcHWFcngSlU2BjR04oyuH
c/SSiLSB3YB0tdFGta5uorXyV1y7RElPeBfOfvEjsG3TAgMBAAGjUzBRMB0GA1Ud
DgQWBBQX+xlgSWWbDjv0SrJ+h67xauJ80zAfBgNVHSMEGDAWgBQX+xlgSWWbDjv0
SrJ+h67xauJ80zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw
ZG2MCCjscn6h/QOoJU+IDfa68OqLq0I37gMnLMde4yEhAmm//miePIq4Uz9GRJ+h
rAmdEnspKgyQ93PjF7Xpk/JdJA4B1bIrsOl/cSwqx2sFhRt8Kt1DHGlGWXqOaHRP
UkZ86MyRL3sXly6WkxEYxZJeQaOzMy2XmQh7grzrlTBuSI+0xf7vsRRDipxr6LVQ
6qGWyGODLLc2JD1IXj/1HpRVT2LoGGlKMuyxACQAm4oak1vvJ9mGxgfd9AU+eo58
O/esB2Eaf+QqMPELdFSZQfG2jvp+3WQTZK8fDKHyLr076G3UetEMy867F6fzTSZd
9Kxq0DY7RCEpdHMCKcOL
-----END CERTIFICATE-----
turn:
secret: secret://turn.secret
linkDevice:
secret: secret://linkDevice.secret
maxmindCityDatabase:
type: static
callingTurnDnsRecords:
type: static
callingTurnPerformanceTable:
type: static
callingTurnManualTable:
type: static
noiseTunnel:
port: 8443
recognizedProxySecret: secret://noiseTunnel.recognizedProxySecret
externalRequestFilter:
grpcMethods:
- com.example.grpc.ExampleService/exampleMethod
paths:
- /example
permittedInternalRanges:
- 127.0.0.0/8