diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 66243740..bc60ea6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,5 +22,8 @@ jobs: # 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 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 run: ./mvnw -e -B verify diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000..1ece4c85 --- /dev/null +++ b/TESTING.md @@ -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 diff --git a/integration-tests/src/main/java/org/signal/integration/IntegrationTools.java b/integration-tests/src/main/java/org/signal/integration/IntegrationTools.java index 109a8480..160022ca 100644 --- a/integration-tests/src/main/java/org/signal/integration/IntegrationTools.java +++ b/integration-tests/src/main/java/org/signal/integration/IntegrationTools.java @@ -15,7 +15,6 @@ import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; import org.whispersystems.textsecuregcm.storage.VerificationSessionManager; 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.DefaultCredentialsProvider; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; @@ -31,13 +30,9 @@ public class IntegrationTools { public static IntegrationTools create(final Config config) { final AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build(); - final DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( - config.dynamoDbClientConfiguration(), - credentialsProvider); + final DynamoDbAsyncClient dynamoDbAsyncClient = config.dynamoDbClient().buildAsyncClient(credentialsProvider); - final DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( - config.dynamoDbClientConfiguration(), - credentialsProvider); + final DynamoDbClient dynamoDbClient = config.dynamoDbClient().buildSyncClient(credentialsProvider); final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient); diff --git a/integration-tests/src/main/java/org/signal/integration/config/Config.java b/integration-tests/src/main/java/org/signal/integration/config/Config.java index ec02e1cb..104f8a54 100644 --- a/integration-tests/src/main/java/org/signal/integration/config/Config.java +++ b/integration-tests/src/main/java/org/signal/integration/config/Config.java @@ -5,11 +5,11 @@ package org.signal.integration.config; -import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration; +import org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory; public record Config(String domain, String rootCert, - DynamoDbClientConfiguration dynamoDbClientConfiguration, + DynamoDbClientFactory dynamoDbClient, DynamoDbTables dynamoDbTables, String prescribedRegistrationNumber, String prescribedRegistrationCode) { diff --git a/service/config/sample.yml b/service/config/sample.yml index a44dd3ce..3bddf548 100644 --- a/service/config/sample.yml +++ b/service/config/sample.yml @@ -67,14 +67,15 @@ braintree: supportedCurrenciesByPaymentMethod: PAYPAL: - usd - pubSubProject: example-project - pubSubTopic: example-topic - pubSubCredentialConfiguration: | - { - "credential": "configuration" - } + pubSubPublisher: + project: example-project + topic: example-topic + credentialConfiguration: | + { + "credential": "configuration" + } -dynamoDbClientConfiguration: +dynamoDbClient: region: us-west-2 # AWS Region dynamoDbTables: @@ -138,8 +139,9 @@ cacheCluster: # Redis server configuration for cache cluster clientPresenceCluster: # Redis server configuration for client presence cluster configurationUri: redis://redis.example.com:6379/ -pubsub: # Redis server configuration for pubsub cluster - uri: redis://redis.example.com:6379/ +provisioning: + pubsub: # Redis server configuration for pubsub cluster + uri: redis://redis.example.com:6379/ pushSchedulerCluster: # Redis server configuration for push scheduler cluster configurationUri: redis://redis.example.com:6379/ @@ -218,9 +220,10 @@ metricsCluster: configurationUri: redis://redis.example.com:6379/ awsAttachments: # AWS S3 configuration - accessKey: secret://awsAttachments.accessKey - accessSecret: secret://awsAttachments.accessSecret bucket: aws-attachments + credentials: + accessKeyId: secret://awsAttachments.accessKey + secretAccessKey: secret://awsAttachments.accessSecret region: us-west-2 gcpAttachments: # GCP Storage configuration @@ -245,9 +248,10 @@ fcm: # FCM configuration credentials: secret://fcm.credentials cdn: - accessKey: secret://cdn.accessKey - accessSecret: secret://cdn.accessSecret bucket: cdn # S3 Bucket name + credentials: + accessKeyId: secret://cdn.accessKey + secretAccessKey: secret://cdn.accessSecret region: us-west-2 # AWS region clientCdn: @@ -345,13 +349,14 @@ remoteConfig: paymentsService: userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret - fixerApiKey: secret://paymentsService.fixerApiKey - coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey - coinMarketCapCurrencyIds: - MOB: 7878 paymentCurrencies: # list of symbols for supported currencies - MOB + externalClients: + fixerApiKey: secret://paymentsService.fixerApiKey + coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey + coinMarketCapCurrencyIds: + MOB: 7878 artService: userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret diff --git a/service/pom.xml b/service/pom.xml index f2cb3073..0b78c84c 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -640,6 +640,31 @@ + + test-server + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + start-test-server + integration-test + + java + + + org.whispersystems.textsecuregcm.LocalWhisperServerService + test + + + + + + + @@ -692,15 +717,15 @@ java + + org.whispersystems.textsecuregcm.CheckServiceConfigurations + test + + ${project.basedir}/config + + - - org.whispersystems.textsecuregcm.CheckServiceConfigurations - test - - ${project.basedir}/config - - diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index cfc9c05e..dd17ed00 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -15,37 +15,40 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.whispersystems.textsecuregcm.attachments.TusConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; -import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration; import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration; +import org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory; import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration; import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration; import org.whispersystems.textsecuregcm.configuration.Cdn3StorageManagerConfiguration; import org.whispersystems.textsecuregcm.configuration.CdnConfiguration; import org.whispersystems.textsecuregcm.configuration.ClientCdnConfiguration; 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.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.ExternalRequestFilterConfiguration; +import org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory; import org.whispersystems.textsecuregcm.configuration.FcmConfiguration; import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration; 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.MaxDeviceConfiguration; import org.whispersystems.textsecuregcm.configuration.MessageByteLimitCardinalityEstimatorConfiguration; import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration; -import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration; import org.whispersystems.textsecuregcm.configuration.NoiseWebSocketTunnelConfiguration; import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration; import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration; -import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration; -import org.whispersystems.textsecuregcm.configuration.RedisConfiguration; -import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration; +import org.whispersystems.textsecuregcm.configuration.ProvisioningConfiguration; +import org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory; import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration; import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration; +import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory; import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration; import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery3Configuration; @@ -69,6 +72,11 @@ public class WhisperServerConfiguration extends Configuration { @JsonProperty private TlsKeyStoreConfiguration tlsKeyStore; + @NotNull + @Valid + @JsonProperty + AwsCredentialsProviderFactory awsCredentialsProvider = new DefaultAwsCredentialsFactory(); + @NotNull @Valid @JsonProperty @@ -82,7 +90,7 @@ public class WhisperServerConfiguration extends Configuration { @NotNull @Valid @JsonProperty - private DynamoDbClientConfiguration dynamoDbClientConfiguration; + private DynamoDbClientFactory dynamoDbClient; @NotNull @Valid @@ -117,22 +125,22 @@ public class WhisperServerConfiguration extends Configuration { @NotNull @Valid @JsonProperty - private DogstatsdConfiguration dogstatsd = new DogstatsdConfiguration(); + private DatadogConfiguration dogstatsd = new DogstatsdConfiguration(); @NotNull @Valid @JsonProperty - private RedisClusterConfiguration cacheCluster; + private FaultTolerantRedisClusterFactory cacheCluster; @NotNull @Valid @JsonProperty - private RedisConfiguration pubsub; + private FaultTolerantRedisClusterFactory metricsCluster; @NotNull @Valid @JsonProperty - private RedisClusterConfiguration metricsCluster; + private ProvisioningConfiguration provisioning; @NotNull @Valid @@ -151,12 +159,12 @@ public class WhisperServerConfiguration extends Configuration { @NotNull @Valid @JsonProperty - private RedisClusterConfiguration pushSchedulerCluster; + private FaultTolerantRedisClusterFactory pushSchedulerCluster; @NotNull @Valid @JsonProperty - private RedisClusterConfiguration rateLimitersCluster; + private FaultTolerantRedisClusterFactory rateLimitersCluster; @NotNull @Valid @@ -166,7 +174,7 @@ public class WhisperServerConfiguration extends Configuration { @NotNull @Valid @JsonProperty - private RedisClusterConfiguration clientPresenceCluster; + private FaultTolerantRedisClusterFactory clientPresenceCluster; @Valid @NotNull @@ -201,7 +209,7 @@ public class WhisperServerConfiguration extends Configuration { @Valid @NotNull @JsonProperty - private HCaptchaConfiguration hCaptcha; + private HCaptchaClientFactory hCaptcha; @Valid @NotNull @@ -246,7 +254,7 @@ public class WhisperServerConfiguration extends Configuration { @Valid @NotNull @JsonProperty - private AppConfigConfiguration appConfig; + private DynamicConfigurationManagerFactory appConfig; @Valid @NotNull @@ -270,12 +278,12 @@ public class WhisperServerConfiguration extends Configuration { @Valid @JsonProperty - private SpamFilterConfiguration spamFilterConfiguration; + private SpamFilterConfiguration spamFilter; @Valid @NotNull @JsonProperty - private RegistrationServiceConfiguration registrationService; + private RegistrationServiceClientFactory registrationService; @Valid @NotNull @@ -305,28 +313,28 @@ public class WhisperServerConfiguration extends Configuration { @Valid @NotNull @JsonProperty - private VirtualThreadConfiguration virtualThreadConfiguration = new VirtualThreadConfiguration(Duration.ofMillis(1)); + private VirtualThreadConfiguration virtualThread = new VirtualThreadConfiguration(Duration.ofMillis(1)); @Valid @NotNull @JsonProperty - private MonitoredS3ObjectConfiguration maxmindCityDatabase; + private S3ObjectMonitorFactory maxmindCityDatabase; @Valid @NotNull @JsonProperty - private MonitoredS3ObjectConfiguration callingTurnDnsRecords; + private S3ObjectMonitorFactory callingTurnDnsRecords; @Valid @NotNull @JsonProperty - private MonitoredS3ObjectConfiguration callingTurnPerformanceTable; + private S3ObjectMonitorFactory callingTurnPerformanceTable; @Valid @NotNull @JsonProperty - private MonitoredS3ObjectConfiguration callingTurnManualTable; + private S3ObjectMonitorFactory callingTurnManualTable; @Valid @NotNull @@ -342,6 +350,10 @@ public class WhisperServerConfiguration extends Configuration { return tlsKeyStore; } + public AwsCredentialsProviderFactory getAwsCredentialsConfiguration() { + return awsCredentialsProvider; + } + public StripeConfiguration getStripe() { return stripe; } @@ -350,15 +362,15 @@ public class WhisperServerConfiguration extends Configuration { return braintree; } - public DynamoDbClientConfiguration getDynamoDbClientConfiguration() { - return dynamoDbClientConfiguration; + public DynamoDbClientFactory getDynamoDbClientConfiguration() { + return dynamoDbClient; } public DynamoDbTables getDynamoDbTables() { return dynamoDbTables; } - public HCaptchaConfiguration getHCaptchaConfiguration() { + public HCaptchaClientFactory getHCaptchaConfiguration() { return hCaptcha; } @@ -378,15 +390,15 @@ public class WhisperServerConfiguration extends Configuration { return gcpAttachments; } - public RedisClusterConfiguration getCacheClusterConfiguration() { + public FaultTolerantRedisClusterFactory getCacheClusterConfiguration() { return cacheCluster; } - public RedisConfiguration getPubsubCacheConfiguration() { - return pubsub; + public ProvisioningConfiguration getProvisioningConfiguration() { + return provisioning; } - public RedisClusterConfiguration getMetricsClusterConfiguration() { + public FaultTolerantRedisClusterFactory getMetricsClusterConfiguration() { return metricsCluster; } @@ -410,15 +422,15 @@ public class WhisperServerConfiguration extends Configuration { return messageCache; } - public RedisClusterConfiguration getClientPresenceClusterConfiguration() { + public FaultTolerantRedisClusterFactory getClientPresenceClusterConfiguration() { return clientPresenceCluster; } - public RedisClusterConfiguration getPushSchedulerCluster() { + public FaultTolerantRedisClusterFactory getPushSchedulerCluster() { return pushSchedulerCluster; } - public RedisClusterConfiguration getRateLimitersCluster() { + public FaultTolerantRedisClusterFactory getRateLimitersCluster() { return rateLimitersCluster; } @@ -446,7 +458,7 @@ public class WhisperServerConfiguration extends Configuration { return cdn3StorageManager; } - public DogstatsdConfiguration getDatadogConfiguration() { + public DatadogConfiguration getDatadogConfiguration() { return dogstatsd; } @@ -489,7 +501,7 @@ public class WhisperServerConfiguration extends Configuration { return remoteConfig; } - public AppConfigConfiguration getAppConfig() { + public DynamicConfigurationManagerFactory getAppConfig() { return appConfig; } @@ -510,10 +522,10 @@ public class WhisperServerConfiguration extends Configuration { } public SpamFilterConfiguration getSpamFilterConfiguration() { - return spamFilterConfiguration; + return spamFilter; } - public RegistrationServiceConfiguration getRegistrationServiceConfiguration() { + public RegistrationServiceClientFactory getRegistrationServiceConfiguration() { return registrationService; } @@ -538,22 +550,22 @@ public class WhisperServerConfiguration extends Configuration { } public VirtualThreadConfiguration getVirtualThreadConfiguration() { - return virtualThreadConfiguration; + return virtualThread; } - public MonitoredS3ObjectConfiguration getMaxmindCityDatabase() { + public S3ObjectMonitorFactory getMaxmindCityDatabase() { return maxmindCityDatabase; } - public MonitoredS3ObjectConfiguration getCallingTurnDnsRecords() { + public S3ObjectMonitorFactory getCallingTurnDnsRecords() { return callingTurnDnsRecords; } - public MonitoredS3ObjectConfiguration getCallingTurnPerformanceTable() { + public S3ObjectMonitorFactory getCallingTurnPerformanceTable() { return callingTurnPerformanceTable; } - public MonitoredS3ObjectConfiguration getCallingTurnManualTable() { + public S3ObjectMonitorFactory getCallingTurnManualTable() { return callingTurnManualTable; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index d6b862a7..539d8a6b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -7,14 +7,7 @@ package org.whispersystems.textsecuregcm; import static com.codahale.metrics.MetricRegistry.name; 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.pubsub.v1.TopicName; import io.dropwizard.auth.AuthDynamicFeature; import io.dropwizard.auth.AuthFilter; 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.Environment; import io.dropwizard.jetty.HttpsConnectorFactory; -import io.dropwizard.lifecycle.Managed; import io.grpc.ServerBuilder; import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder; 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.jvm.ExecutorServiceMetrics; import io.netty.channel.local.LocalAddress; -import java.io.ByteArrayInputStream; import java.net.http.HttpClient; -import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; 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.StripeManager; import org.whispersystems.textsecuregcm.util.BufferingInterceptor; -import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; import org.whispersystems.textsecuregcm.util.ManagedAwsCrt; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier; @@ -263,9 +252,7 @@ import org.whispersystems.websocket.WebSocketResourceProviderFactory; import org.whispersystems.websocket.setup.WebSocketEnvironment; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.http.crt.AwsCrtHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; @@ -279,9 +266,6 @@ public class WhisperServerService extends Application bootstrap) { // `SecretStore` needs to be initialized before Dropwizard reads the main application config file. @@ -328,16 +312,15 @@ public class WhisperServerService extends Application dynamicConfigurationManager = - new DynamicConfigurationManager<>(config.getAppConfig().getApplication(), - config.getAppConfig().getEnvironment(), - config.getAppConfig().getConfigurationName(), - DynamicConfiguration.class, dynamicConfigurationExecutor); + DynamicConfigurationManager dynamicConfigurationManager = config.getAppConfig() + .build(DynamicConfiguration.class, dynamicConfigurationExecutor, awsCredentialsProvider); dynamicConfigurationManager.start(); MetricsUtil.configureRegistries(config, environment, dynamicConfigurationManager); @@ -362,11 +345,10 @@ public class WhisperServerService extends Application messageDeletionQueue = new LinkedBlockingQueue<>(); Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(), @@ -428,18 +410,19 @@ public class WhisperServerService extends Application keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(100_000); Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(), @@ -551,14 +534,8 @@ public class WhisperServerService extends Application dynamicConfigurationManager.getConfiguration().getVirtualThreads().allowedPinEvents(), 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, config.getStripe().idempotencyKeyGenerator().value(), config.getStripe().boostDescription(), config.getStripe().supportedCurrenciesByPaymentMethod()); BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(), config.getBraintree().publicKey(), config.getBraintree().privateKey().value(), config.getBraintree().environment(), 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); environment.lifecycle().manage(apnSender); @@ -708,10 +658,7 @@ public class WhisperServerService extends Application, Managed { @@ -38,20 +38,10 @@ public class CallDnsRecordsManager implements Supplier, Managed .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) .build(); - public CallDnsRecordsManager( - @Nonnull final ScheduledExecutorService executorService, - @Nonnull final MonitoredS3ObjectConfiguration configuration - ){ - this.objectMonitor = new S3ObjectMonitor( - configuration.s3Region(), - configuration.s3Bucket(), - configuration.objectKey(), - configuration.maxSize(), - executorService, - configuration.refreshInterval(), - this::handleDatabaseChanged - ); + public CallDnsRecordsManager(final ScheduledExecutorService executorService, + final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration) { + this.objectMonitor = configuration.build(awsCredentialsProvider, executorService); this.callDnsRecords.set(CallDnsRecords.empty()); this.refreshTimer = Metrics.timer(MetricsUtil.name(CallDnsRecordsManager.class, "refresh")); } @@ -74,14 +64,12 @@ public class CallDnsRecordsManager implements Supplier, Managed @Override public void start() throws Exception { - Managed.super.start(); - objectMonitor.start(); + objectMonitor.start(this::handleDatabaseChanged); } @Override public void stop() throws Exception { objectMonitor.stop(); - Managed.super.stop(); callDnsRecords.getAndSet(null); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java index 2e81676f..ff3db32b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java @@ -8,18 +8,18 @@ package org.whispersystems.textsecuregcm.calls.routing; import io.dropwizard.lifecycle.Managed; import io.micrometer.core.instrument.Metrics; 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.InputStream; import java.io.InputStreamReader; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; 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, Managed { @@ -33,21 +33,11 @@ public class CallRoutingTableManager implements Supplier, Mana private static final Logger log = LoggerFactory.getLogger(CallRoutingTableManager.class); - public CallRoutingTableManager( - @Nonnull final ScheduledExecutorService executorService, - @Nonnull final MonitoredS3ObjectConfiguration configuration, - @Nonnull final String tableTag - ){ - this.objectMonitor = new S3ObjectMonitor( - configuration.s3Region(), - configuration.s3Bucket(), - configuration.objectKey(), - configuration.maxSize(), - executorService, - configuration.refreshInterval(), - this::handleDatabaseChanged - ); + public CallRoutingTableManager(final ScheduledExecutorService executorService, + final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration, + final String tableTag) { + this.objectMonitor = configuration.build(awsCredentialsProvider, executorService); this.tableTag = tableTag; this.routingTable.set(CallRoutingTable.empty()); this.refreshTimer = Metrics.timer(MetricsUtil.name(CallRoutingTableManager.class, tableTag)); @@ -67,13 +57,11 @@ public class CallRoutingTableManager implements Supplier, Mana @Override public void start() throws Exception { - Managed.super.start(); - objectMonitor.start(); + objectMonitor.start(this::handleDatabaseChanged); } @Override public void stop() throws Exception { - Managed.super.stop(); objectMonitor.stop(); routingTable.getAndSet(null); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AppConfigConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AppConfigConfiguration.java index 1f4d08c0..68c405b5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AppConfigConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AppConfigConfiguration.java @@ -1,10 +1,14 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; - +import java.util.concurrent.ScheduledExecutorService; 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 @NotEmpty @@ -29,4 +33,11 @@ public class AppConfigConfiguration { public String getConfigurationName() { return configuration; } + + @Override + public DynamicConfigurationManager build(Class klazz, ScheduledExecutorService scheduledExecutorService, + AwsCredentialsProvider awsCredentialsProvider) { + return new DynamicConfigurationManager<>(application, environment, configuration, awsCredentialsProvider, klazz, + scheduledExecutorService); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java index 3bf3f3af..8dd591a7 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java @@ -4,12 +4,11 @@ */ package org.whispersystems.textsecuregcm.configuration; +import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public record AwsAttachmentsConfiguration(@NotNull SecretString accessKey, - @NotNull SecretString accessSecret, +public record AwsAttachmentsConfiguration(@NotNull @Valid StaticAwsCredentialsFactory credentials, @NotBlank String bucket, @NotBlank String region) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsCredentialsProviderFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsCredentialsProviderFactory.java new file mode 100644 index 00000000..d9d46a42 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsCredentialsProviderFactory.java @@ -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(); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java index f838485f..f0ac64ac 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java @@ -32,9 +32,7 @@ public record BraintreeConfiguration(@NotBlank String merchantId, @NotBlank String graphqlUrl, @NotEmpty Map merchantAccounts, @NotNull @Valid CircuitBreakerConfiguration circuitBreaker, - @NotBlank String pubSubProject, - @NotBlank String pubSubTopic, - @NotBlank String pubSubCredentialConfiguration) { + @Valid @NotNull PubSubPublisherFactory pubSubPublisher) { public BraintreeConfiguration { if (circuitBreaker == null) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java index e3bb81cc..93a07a88 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java @@ -5,12 +5,11 @@ package org.whispersystems.textsecuregcm.configuration; +import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public record CdnConfiguration(@NotNull SecretString accessKey, - @NotNull SecretString accessSecret, +public record CdnConfiguration(@NotNull @Valid StaticAwsCredentialsFactory credentials, @NotBlank String bucket, @NotBlank String region) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java new file mode 100644 index 00000000..3a7bb3b5 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java @@ -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(); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DefaultAwsCredentialsFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DefaultAwsCredentialsFactory.java new file mode 100644 index 00000000..3bac0869 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DefaultAwsCredentialsFactory.java @@ -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(); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DefaultPubSubPublisherFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DefaultPubSubPublisherFactory.java new file mode 100644 index 00000000..93cec6b8 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DefaultPubSubPublisherFactory.java @@ -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(); + } + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DogstatsdConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DogstatsdConfiguration.java index 0c038c91..906f47de 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DogstatsdConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DogstatsdConfiguration.java @@ -6,13 +6,14 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; -import io.micrometer.statsd.StatsdConfig; +import com.fasterxml.jackson.annotation.JsonTypeName; import io.micrometer.statsd.StatsdFlavor; import java.time.Duration; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -public class DogstatsdConfiguration implements StatsdConfig { +@JsonTypeName("default") +public class DogstatsdConfiguration implements DatadogConfiguration { @JsonProperty @NotNull @@ -31,6 +32,7 @@ public class DogstatsdConfiguration implements StatsdConfig { return step; } + @Override public String getEnvironment() { return environment; } @@ -50,4 +52,9 @@ public class DogstatsdConfiguration implements StatsdConfig { public String host() { return host; } + + @Override + public Duration getShutdownWaitDuration() { + return step().plus(step.dividedBy(2)); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamicConfigurationManagerFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamicConfigurationManagerFactory.java new file mode 100644 index 00000000..406428a9 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamicConfigurationManagerFactory.java @@ -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 { + + DynamicConfigurationManager build(Class configurationClass, + ScheduledExecutorService scheduledExecutorService, AwsCredentialsProvider awsCredentialsProvider); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientConfiguration.java index 804e5250..7cff3be1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientConfiguration.java @@ -9,11 +9,20 @@ import java.time.Duration; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; 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, @NotNull Duration clientExecutionTimeout, @NotNull Duration clientRequestTimeout, - @Positive int maxConnections) { + @Positive int maxConnections) implements DynamoDbClientFactory { public DynamoDbClientConfiguration { if (clientExecutionTimeout == null) { @@ -28,4 +37,32 @@ public record DynamoDbClientConfiguration(@NotBlank String region, 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(); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientFactory.java new file mode 100644 index 00000000..c2f7b75d --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DynamoDbClientFactory.java @@ -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); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FaultTolerantRedisClusterFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FaultTolerantRedisClusterFactory.java new file mode 100644 index 00000000..98f2847e --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FaultTolerantRedisClusterFactory.java @@ -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); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaClientFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaClientFactory.java new file mode 100644 index 00000000..1e67f186 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaClientFactory.java @@ -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 dynamicConfigurationManager); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java index d5a334e3..0d401ef1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java @@ -7,9 +7,15 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; 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.storage.DynamicConfigurationManager; +import java.util.concurrent.ScheduledExecutorService; -public class HCaptchaConfiguration { +@JsonTypeName("default") +public class HCaptchaConfiguration implements HCaptchaClientFactory { @JsonProperty @NotNull @@ -36,4 +42,14 @@ public class HCaptchaConfiguration { return retry; } + @Override + public HCaptchaClient build(final ScheduledExecutorService retryExecutor, + final DynamicConfigurationManager dynamicConfigurationManager) { + return new HCaptchaClient( + apiKey.value(), + retryExecutor, + circuitBreaker, + retry, + dynamicConfigurationManager); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MessageCacheConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MessageCacheConfiguration.java index 8f967b33..82146f1a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MessageCacheConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MessageCacheConfiguration.java @@ -15,12 +15,12 @@ public class MessageCacheConfiguration { @JsonProperty @NotNull @Valid - private RedisClusterConfiguration cluster; + private FaultTolerantRedisClusterFactory cluster; @JsonProperty private int persistDelayMinutes = 10; - public RedisClusterConfiguration getRedisClusterConfiguration() { + public FaultTolerantRedisClusterFactory getRedisClusterConfiguration() { return cluster; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MonitoredS3ObjectConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MonitoredS3ObjectConfiguration.java index 53a0ecf8..9e5e6145 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MonitoredS3ObjectConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/MonitoredS3ObjectConfiguration.java @@ -5,18 +5,23 @@ 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.util.concurrent.ScheduledExecutorService; import javax.validation.constraints.NotBlank; +@JsonTypeName("default") public record MonitoredS3ObjectConfiguration( @NotBlank String s3Region, @NotBlank String s3Bucket, @NotBlank String objectKey, Long maxSize, - Duration refreshInterval) { + Duration refreshInterval) implements S3ObjectMonitorFactory { - private static long DEFAULT_MAXSIZE = 16*1024*1024; - private static Duration DEFAULT_REFRESH_INTERVAL = Duration.ofMinutes(5); + private static final long DEFAULT_MAXSIZE = 16 * 1024 * 1024; + private static final Duration DEFAULT_REFRESH_INTERVAL = Duration.ofMinutes(5); public MonitoredS3ObjectConfiguration { if (maxSize == null) { @@ -26,4 +31,12 @@ public record MonitoredS3ObjectConfiguration( refreshInterval = DEFAULT_REFRESH_INTERVAL; } } + + @Override + public S3ObjectMonitor build(final AwsCredentialsProvider awsCredentialsProvider, + final ScheduledExecutorService refreshExecutorService) { + + return new S3ObjectMonitor(awsCredentialsProvider, s3Region, s3Bucket, objectKey, maxSize, refreshExecutorService, + refreshInterval); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceClientsConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceClientsConfiguration.java new file mode 100644 index 00000000..9669ed08 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceClientsConfiguration.java @@ -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); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceClientsFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceClientsFactory.java new file mode 100644 index 00000000..734d9240 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceClientsFactory.java @@ -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); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java index 06dc78e7..31883feb 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java @@ -5,17 +5,15 @@ package org.whispersystems.textsecuregcm.configuration; -import java.util.List; -import java.util.Map; -import javax.validation.constraints.NotBlank; +import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; -import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; +import java.util.List; public record PaymentsServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret, - @NotNull SecretString coinMarketCapApiKey, - @NotNull SecretString fixerApiKey, - @NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds, - @NotEmpty List paymentCurrencies) { + @NotEmpty List paymentCurrencies, + @NotNull @Valid PaymentsServiceClientsFactory externalClients) { + + } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ProvisioningConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ProvisioningConfiguration.java new file mode 100644 index 00000000..ceb15a1f --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ProvisioningConfiguration.java @@ -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(); + } + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PubSubPublisherFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PubSubPublisherFactory.java new file mode 100644 index 00000000..9a4900b3 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PubSubPublisherFactory.java @@ -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; +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisClusterConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisClusterConfiguration.java index ff74d53a..3e401be7 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisClusterConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisClusterConfiguration.java @@ -6,13 +6,17 @@ package org.whispersystems.textsecuregcm.configuration; 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.constraints.NotEmpty; 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 @NotEmpty @@ -32,6 +36,11 @@ public class RedisClusterConfiguration { @Valid private RetryConfiguration retry = new RetryConfiguration(); + @VisibleForTesting + void setConfigurationUri(final String configurationUri) { + this.configurationUri = configurationUri; + } + public String getConfigurationUri() { return configurationUri; } @@ -47,4 +56,9 @@ public class RedisClusterConfiguration { public RetryConfiguration getRetryConfiguration() { return retry; } + + @Override + public FaultTolerantRedisCluster build(final String name, final ClientResources.Builder clientResourcesBuilder) { + return new FaultTolerantRedisCluster(name, this, clientResourcesBuilder); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisConfiguration.java index cf0d3b94..74bbc9ff 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RedisConfiguration.java @@ -6,13 +6,16 @@ package org.whispersystems.textsecuregcm.configuration; 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.util.List; -import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.redis.RedisUriUtil; -public class RedisConfiguration { +@JsonTypeName("default") +public class RedisConfiguration implements SingletonRedisClientFactory { @JsonProperty @NotEmpty @@ -22,11 +25,6 @@ public class RedisConfiguration { @NotNull private Duration timeout = Duration.ofSeconds(1); - @JsonProperty - @NotNull - @Valid - private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration(); - public String getUri() { return uri; } @@ -35,7 +33,12 @@ public class RedisConfiguration { return timeout; } - public CircuitBreakerConfiguration getCircuitBreakerConfiguration() { - return circuitBreaker; + @Override + public RedisClient build(final ClientResources clientResources) { + final RedisClient redisClient = RedisClient.create(clientResources, + RedisUriUtil.createRedisUriWithTimeout(uri, timeout)); + redisClient.setDefaultTimeout(timeout); + + return redisClient; } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceClientFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceClientFactory.java new file mode 100644 index 00000000..3e18b9c9 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceClientFactory.java @@ -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); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceConfiguration.java index cb50f6f2..44cc4908 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RegistrationServiceConfiguration.java @@ -1,10 +1,35 @@ 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 org.whispersystems.textsecuregcm.registration.IdentityTokenCallCredentials; +import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient; +@JsonTypeName("default") public record RegistrationServiceConfiguration(@NotBlank String host, int port, @NotBlank String credentialConfigurationJson, @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); + } + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/S3ObjectMonitorFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/S3ObjectMonitorFactory.java new file mode 100644 index 00000000..3b31ec5d --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/S3ObjectMonitorFactory.java @@ -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); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SingletonRedisClientFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SingletonRedisClientFactory.java new file mode 100644 index 00000000..3eb90f02 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SingletonRedisClientFactory.java @@ -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); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StaticAwsCredentialsFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StaticAwsCredentialsFactory.java new file mode 100644 index 00000000..269699a8 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StaticAwsCredentialsFactory.java @@ -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())); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/geo/MaxMindDatabaseManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/geo/MaxMindDatabaseManager.java index 57a9f15f..a58ba3b2 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/geo/MaxMindDatabaseManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/geo/MaxMindDatabaseManager.java @@ -7,25 +7,24 @@ package org.whispersystems.textsecuregcm.geo; import com.maxmind.db.CHMCache; import com.maxmind.geoip2.DatabaseReader; -import com.maxmind.geoip2.GeoIp2Provider; import io.dropwizard.lifecycle.Managed; import io.micrometer.core.instrument.Metrics; 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.IOException; import java.io.InputStream; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; 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, Managed { @@ -39,21 +38,11 @@ public class MaxMindDatabaseManager implements Supplier, Managed private static final Logger log = LoggerFactory.getLogger(MaxMindDatabaseManager.class); - public MaxMindDatabaseManager( - @Nonnull final ScheduledExecutorService executorService, - @Nonnull final MonitoredS3ObjectConfiguration configuration, - @Nonnull final String databaseTag - ){ - this.databaseMonitor = new S3ObjectMonitor( - configuration.s3Region(), - configuration.s3Bucket(), - configuration.objectKey(), - configuration.maxSize(), - executorService, - configuration.refreshInterval(), - this::handleDatabaseChanged - ); + public MaxMindDatabaseManager(final ScheduledExecutorService executorService, + final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration, + final String databaseTag) { + this.databaseMonitor = configuration.build(awsCredentialsProvider, executorService); this.databaseTag = databaseTag; this.refreshTimer = Metrics.timer(MetricsUtil.name(MaxMindDatabaseManager.class, "refresh"), "db", databaseTag); } @@ -93,17 +82,15 @@ public class MaxMindDatabaseManager implements Supplier, Managed @Override public void start() throws Exception { - Managed.super.start(); - databaseMonitor.start(); + databaseMonitor.start(this::handleDatabaseChanged); } @Override public void stop() throws Exception { - Managed.super.stop(); databaseMonitor.stop(); final DatabaseReader reader = databaseReader.getAndSet(null); - if(reader != null) { + if (reader != null) { reader.close(); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MetricsUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MetricsUtil.java index bed77585..00f38f2e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MetricsUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MetricsUtil.java @@ -66,7 +66,8 @@ public class MetricsUtil { environment.lifecycle().addEventListener(new ApplicationShutdownMonitor(Metrics.globalRegistry)); environment.lifecycle().addEventListener( - new MicrometerRegistryManager(Metrics.globalRegistry, config.getDatadogConfiguration().pollingFrequency())); + new MicrometerRegistryManager(Metrics.globalRegistry, + config.getDatadogConfiguration().getShutdownWaitDuration())); } @VisibleForTesting diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MicrometerRegistryManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MicrometerRegistryManager.java index 0bf6798d..0479b0bf 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MicrometerRegistryManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MicrometerRegistryManager.java @@ -14,14 +14,13 @@ import org.slf4j.LoggerFactory; public class MicrometerRegistryManager implements LifeCycle.Listener { private static final Logger logger = LoggerFactory.getLogger(MicrometerRegistryManager.class); - private static final Duration BUFFER = Duration.ofSeconds(5); private final MeterRegistry meterRegistry; private final Duration waitDuration; - public MicrometerRegistryManager(final MeterRegistry meterRegistry, final Duration pollingFrequency) { + public MicrometerRegistryManager(final MeterRegistry meterRegistry, final Duration waitDuration) { this.meterRegistry = meterRegistry; - this.waitDuration = pollingFrequency.plus(BUFFER); + this.waitDuration = waitDuration; } @Override diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/ProvisioningManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/ProvisioningManager.java index 3db915a6..57251f5f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/ProvisioningManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/ProvisioningManager.java @@ -7,7 +7,6 @@ package org.whispersystems.textsecuregcm.push; import static com.codahale.metrics.MetricRegistry.name; -import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; 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.pubsub.RedisPubSubAdapter; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; -import io.lettuce.core.resource.ClientResources; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tags; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -29,7 +26,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; import org.whispersystems.textsecuregcm.redis.RedisOperation; -import org.whispersystems.textsecuregcm.redis.RedisUriUtil; import org.whispersystems.textsecuregcm.storage.PubSubProtos; import org.whispersystems.textsecuregcm.util.CircuitBreakerUtil; import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; @@ -56,22 +52,10 @@ public class ProvisioningManager extends RedisPubSubAdapter impl private static final Logger logger = LoggerFactory.getLogger(ProvisioningManager.class); - public ProvisioningManager(final String redisUri, - 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, + public ProvisioningManager(final RedisClient redisClient, final CircuitBreakerConfiguration circuitBreakerConfiguration) { this.redisClient = redisClient; - this.redisClient.setDefaultTimeout(timeout); this.subscriptionConnection = redisClient.connectPubSub(new ByteArrayCodec()); this.publicationConnection = redisClient.connect(new ByteArrayCodec()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/registration/IdentityTokenCallCredentials.java b/service/src/main/java/org/whispersystems/textsecuregcm/registration/IdentityTokenCallCredentials.java index 403107db..5e76f16e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/registration/IdentityTokenCallCredentials.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/registration/IdentityTokenCallCredentials.java @@ -7,13 +7,13 @@ package org.whispersystems.textsecuregcm.registration; import com.google.auth.oauth2.ExternalAccountCredentials; import com.google.auth.oauth2.ImpersonatedCredentials; import com.google.common.annotations.VisibleForTesting; +import io.dropwizard.lifecycle.Managed; import io.github.resilience4j.core.IntervalFunction; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryConfig; import io.grpc.CallCredentials; import io.grpc.Metadata; import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -28,7 +28,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; 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_REFRESH_BUFFER = Duration.ofMinutes(10); @@ -58,7 +58,7 @@ class IdentityTokenCallCredentials extends CallCredentials implements Closeable TimeUnit.MILLISECONDS); } - static IdentityTokenCallCredentials fromCredentialConfig( + public static IdentityTokenCallCredentials fromCredentialConfig( final String credentialConfigJson, final String audience, final ScheduledExecutorService scheduledExecutorService) throws IOException { @@ -129,7 +129,7 @@ class IdentityTokenCallCredentials extends CallCredentials implements Closeable } @Override - public void close() { + public void stop() { synchronized (this) { if (!scheduledFuture.isDone()) { scheduledFuture.cancel(true); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java index d52d25d8..b3d14187 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java @@ -8,6 +8,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; import com.google.protobuf.ByteString; import io.dropwizard.lifecycle.Managed; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.Deadline; import io.grpc.Grpc; @@ -21,7 +22,6 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; @@ -38,7 +38,6 @@ import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession; public class RegistrationServiceClient implements Managed { private final ManagedChannel channel; - private final IdentityTokenCallCredentials identityTokenCallCredentials; private final RegistrationServiceGrpc.RegistrationServiceFutureStub stub; private final Executor callbackExecutor; @@ -61,11 +60,9 @@ public class RegistrationServiceClient implements Managed { public RegistrationServiceClient(final String host, final int port, - final String credentialConfigJson, - final String identityTokenAudience, + final CallCredentials callCredentials, final String caCertificatePem, - final Executor callbackExecutor, - final ScheduledExecutorService identityRefreshExecutor) throws IOException { + final Executor callbackExecutor) throws IOException { try (final ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(caCertificatePem.getBytes(StandardCharsets.UTF_8))) { final ChannelCredentials tlsChannelCredentials = TlsChannelCredentials.newBuilder() @@ -77,10 +74,7 @@ public class RegistrationServiceClient implements Managed { .build(); } - this.identityTokenCallCredentials = IdentityTokenCallCredentials.fromCredentialConfig( - credentialConfigJson, identityTokenAudience, identityRefreshExecutor); - - this.stub = RegistrationServiceGrpc.newFutureStub(channel).withCallCredentials(identityTokenCallCredentials); + this.stub = RegistrationServiceGrpc.newFutureStub(channel).withCallCredentials(callCredentials); this.callbackExecutor = callbackExecutor; } @@ -284,6 +278,5 @@ public class RegistrationServiceClient implements Managed { if (channel != null) { channel.shutdown(); } - this.identityTokenCallCredentials.close(); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/s3/ManagedSupplier.java b/service/src/main/java/org/whispersystems/textsecuregcm/s3/ManagedSupplier.java deleted file mode 100644 index 27920f26..00000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/s3/ManagedSupplier.java +++ /dev/null @@ -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 extends Supplier, Managed { - - @Override - default void start() throws Exception { - // noop - } - - @Override - default void stop() throws Exception { - // noop - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3MonitoringSupplier.java b/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3MonitoringSupplier.java deleted file mode 100644 index 1111c80d..00000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3MonitoringSupplier.java +++ /dev/null @@ -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 implements ManagedSupplier { - - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - @Nonnull - private final Timer refreshTimer; - - @Nonnull - private final Counter refreshErrors; - - @Nonnull - private final AtomicReference holder; - - @Nonnull - private final S3ObjectMonitor monitor; - - @Nonnull - private final Function parser; - - - public S3MonitoringSupplier( - @Nonnull final ScheduledExecutorService executor, - @Nonnull final MonitoredS3ObjectConfiguration cfg, - @Nonnull final Function 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(); - } - }); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitor.java b/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitor.java index 05033e10..d997ad99 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitor.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitor.java @@ -17,7 +17,7 @@ import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; 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.regions.Region; import software.amazon.awssdk.services.s3.S3Client; @@ -39,8 +39,6 @@ public class S3ObjectMonitor { private final Duration refreshInterval; private ScheduledFuture refreshFuture; - private final Consumer changeListener; - private final AtomicReference lastETag = new AtomicReference<>(); private final S3Client s3Client; @@ -48,24 +46,23 @@ public class S3ObjectMonitor { private static final Logger log = LoggerFactory.getLogger(S3ObjectMonitor.class); public S3ObjectMonitor( + final AwsCredentialsProvider awsCredentialsProvider, final String s3Region, final String s3Bucket, final String objectKey, final long maxObjectSize, final ScheduledExecutorService refreshExecutorService, - final Duration refreshInterval, - final Consumer changeListener) { + final Duration refreshInterval) { this(S3Client.builder() .region(Region.of(s3Region)) - .credentialsProvider(WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER) + .credentialsProvider(awsCredentialsProvider) .build(), s3Bucket, objectKey, maxObjectSize, refreshExecutorService, - refreshInterval, - changeListener); + refreshInterval); } @VisibleForTesting @@ -75,8 +72,7 @@ public class S3ObjectMonitor { final String objectKey, final long maxObjectSize, final ScheduledExecutorService refreshExecutorService, - final Duration refreshInterval, - final Consumer changeListener) { + final Duration refreshInterval) { this.s3Client = s3Client; this.s3Bucket = s3Bucket; @@ -85,21 +81,19 @@ public class S3ObjectMonitor { this.refreshExecutorService = refreshExecutorService; this.refreshInterval = refreshInterval; - - this.changeListener = changeListener; } - public synchronized void start() { + public synchronized void start(final Consumer changeListener) { if (refreshFuture != null) { throw new RuntimeException("S3 object manager already started"); } // Run the first request immediately/blocking, then start subsequent calls. log.info("Initial request for s3://{}/{}", s3Bucket, objectKey); - refresh(); + refresh(changeListener); refreshFuture = refreshExecutorService - .scheduleAtFixedRate(this::refresh, refreshInterval.toMillis(), refreshInterval.toMillis(), + .scheduleAtFixedRate(() -> refresh(changeListener), refreshInterval.toMillis(), refreshInterval.toMillis(), TimeUnit.MILLISECONDS); } @@ -139,7 +133,7 @@ public class S3ObjectMonitor { * changed since the last call to {@link #getObject()} or {@code refresh()}. */ @VisibleForTesting - void refresh() { + void refresh(final Consumer changeListener) { try { final HeadObjectResponse objectMetadata = s3Client.headObject(HeadObjectRequest.builder() .bucket(s3Bucket) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java index 94f8a14d..e28f74dc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.util.SystemMapper; 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.services.appconfigdata.AppConfigDataClient; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; @@ -54,9 +55,11 @@ public class DynamicConfigurationManager { private static final Logger logger = LoggerFactory.getLogger(DynamicConfigurationManager.class); public DynamicConfigurationManager(String application, String environment, String configurationName, - Class configurationClass, ScheduledExecutorService scheduledExecutorService) { + AwsCredentialsProvider awsCredentialsProvider, Class configurationClass, + ScheduledExecutorService scheduledExecutorService) { this(AppConfigDataClient .builder() + .credentialsProvider(awsCredentialsProvider) .overrideConfiguration(ClientOverrideConfiguration.builder() .apiCallTimeout(Duration.ofSeconds(10)) .apiCallAttemptTimeout(Duration.ofSeconds(10)).build()) @@ -86,6 +89,9 @@ public class DynamicConfigurationManager { } public void start() { + if (initialized.getCount() == 0) { + return; + } configuration.set(retrieveInitialDynamicConfiguration()); initialized.countDown(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java index 727ed3e2..63960a02 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java @@ -19,8 +19,10 @@ import com.braintreegateway.TransactionSearchRequest; import com.braintreegateway.exceptions.BraintreeException; import com.braintreegateway.exceptions.NotFoundException; 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.pubsub.v1.PubsubMessage; +import io.micrometer.core.instrument.Metrics; import java.math.BigDecimal; import java.time.Duration; import java.time.Instant; @@ -40,8 +42,6 @@ import javax.annotation.Nullable; import javax.ws.rs.ClientErrorException; import javax.ws.rs.WebApplicationException; 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.LoggerFactory; import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; @@ -65,7 +65,7 @@ public class BraintreeManager implements SubscriptionProcessorManager { private final BraintreeGateway braintreeGateway; private final BraintreeGraphqlClient braintreeGraphqlClient; private final CurrencyConversionManager currencyConversionManager; - private final Publisher pubsubPublisher; + private final PublisherInterface pubsubPublisher; private final Executor executor; private final Map> supportedCurrenciesByPaymentMethod; private final Map currenciesToMerchantAccounts; @@ -79,7 +79,7 @@ public class BraintreeManager implements SubscriptionProcessorManager { final Map currenciesToMerchantAccounts, final String graphqlUri, final CurrencyConversionManager currencyConversionManager, - final Publisher pubsubPublisher, + final PublisherInterface pubsubPublisher, final CircuitBreakerConfiguration circuitBreakerConfiguration, final Executor executor, final ScheduledExecutorService retryExecutor) { @@ -107,7 +107,7 @@ public class BraintreeManager implements SubscriptionProcessorManager { BraintreeManager(final BraintreeGateway braintreeGateway, final Map> supportedCurrenciesByPaymentMethod, final Map currenciesToMerchantAccounts, final BraintreeGraphqlClient braintreeGraphqlClient, - final CurrencyConversionManager currencyConversionManager, final Publisher pubsubPublisher, + final CurrencyConversionManager currencyConversionManager, final PublisherInterface pubsubPublisher, final Executor executor) { this.braintreeGateway = braintreeGateway; this.supportedCurrenciesByPaymentMethod = supportedCurrenciesByPaymentMethod; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/DynamoDbFromConfig.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/DynamoDbFromConfig.java deleted file mode 100644 index 804dcd6b..00000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/DynamoDbFromConfig.java +++ /dev/null @@ -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(); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java index e67f2aac..a7fa13ab 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java @@ -23,7 +23,6 @@ import java.util.concurrent.ScheduledExecutorService; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.WhisperServerService; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; 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.UsernameHashNotAvailableException; import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException; -import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig; import org.whispersystems.textsecuregcm.util.ExceptionUtils; import reactor.core.scheduler.Scheduler; 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.DynamoDbClient; @@ -97,18 +96,19 @@ public class AssignUsernameCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( - configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), - configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class, dynamicConfigurationExecutor); + DynamicConfigurationManager dynamicConfigurationManager = configuration.getAppConfig().build( + DynamicConfiguration.class, dynamicConfigurationExecutor, awsCredentialsProvider); dynamicConfigurationManager.start(); final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder(); - FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", - configuration.getCacheClusterConfiguration(), redisClientResourcesBuilder); + FaultTolerantRedisCluster cacheCluster = configuration.getCacheClusterConfiguration().build("main_cache_cluster", + redisClientResourcesBuilder); Scheduler messageDeliveryScheduler = Schedulers.fromExecutorService( environment.lifecycle().executorService("messageDelivery-%d").maxThreads(4) @@ -135,11 +135,11 @@ public class AssignUsernameCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( - configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), - configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class, dynamicConfigurationExecutor); + DynamicConfigurationManager dynamicConfigurationManager = configuration.getAppConfig().build( + DynamicConfiguration.class, dynamicConfigurationExecutor, awsCredentialsProvider); dynamicConfigurationManager.start(); MetricsUtil.configureRegistries(configuration, environment, dynamicConfigurationManager); final ClientResources.Builder redisClientResourcesBuilder = ClientResources.builder(); - FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache", - configuration.getCacheClusterConfiguration(), redisClientResourcesBuilder); + FaultTolerantRedisCluster cacheCluster = configuration.getCacheClusterConfiguration().build("main_cache", + redisClientResourcesBuilder); ScheduledExecutorService recurringJobExecutor = environment.lifecycle() .scheduledExecutorService(name(name, "recurringJob-%d")).threads(2).build(); @@ -124,11 +124,11 @@ record CommandDependencies( ExternalServiceCredentialsGenerator secureValueRecoveryCredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator( configuration.getSvr2Configuration()); - DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( - configuration.getDynamoDbClientConfiguration(), WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER); + DynamoDbAsyncClient dynamoDbAsyncClient = configuration.getDynamoDbClientConfiguration() + .buildAsyncClient(awsCredentialsProvider); - DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( - configuration.getDynamoDbClientConfiguration(), WhisperServerService.AWSSDK_CREDENTIALS_PROVIDER); + DynamoDbClient dynamoDbClient = configuration.getDynamoDbClientConfiguration() + .buildSyncClient(awsCredentialsProvider); RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords( configuration.getDynamoDbTables().getRegistrationRecovery().getTableName(), @@ -163,12 +163,12 @@ record CommandDependencies( configuration.getDynamoDbTables().getMessages().getTableName(), configuration.getDynamoDbTables().getMessages().getExpiration(), messageDeletionExecutor); - FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages", - configuration.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClientResourcesBuilder); - FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence", - configuration.getClientPresenceClusterConfiguration(), redisClientResourcesBuilder); - FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", - configuration.getRateLimitersCluster(), redisClientResourcesBuilder); + FaultTolerantRedisCluster messagesCluster = configuration.getMessageCacheConfiguration() + .getRedisClusterConfiguration().build("messages", redisClientResourcesBuilder); + FaultTolerantRedisCluster clientPresenceCluster = configuration.getClientPresenceClusterConfiguration() + .build("client_presence", redisClientResourcesBuilder); + FaultTolerantRedisCluster rateLimitersCluster = configuration.getRateLimitersCluster().build("rate_limiters", + redisClientResourcesBuilder); SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client( secureValueRecoveryCredentialsGenerator, secureValueRecoveryServiceExecutor, secureValueRecoveryServiceRetryExecutor, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/ScheduledApnPushNotificationSenderServiceCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/ScheduledApnPushNotificationSenderServiceCommand.java index 5f61686c..26dfa09c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/ScheduledApnPushNotificationSenderServiceCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/ScheduledApnPushNotificationSenderServiceCommand.java @@ -63,8 +63,8 @@ public class ScheduledApnPushNotificationSenderServiceCommand extends ServerComm }); } - final FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", - configuration.getPushSchedulerCluster(), deps.redisClusterClientResourcesBuilder()); + final FaultTolerantRedisCluster pushSchedulerCluster = configuration.getPushSchedulerCluster() + .build("push_scheduler", deps.redisClusterClientResourcesBuilder()); final ExecutorService apnSenderExecutor = environment.lifecycle().executorService(name(getClass(), "apnSender-%d")) .maxThreads(1).minThreads(1).build(); diff --git a/service/src/main/resources/META-INF/services/io.dropwizard.jackson.Discoverable b/service/src/main/resources/META-INF/services/io.dropwizard.jackson.Discoverable new file mode 100644 index 00000000..bc79249e --- /dev/null +++ b/service/src/main/resources/META-INF/services/io.dropwizard.jackson.Discoverable @@ -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 diff --git a/service/src/main/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory b/service/src/main/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory new file mode 100644 index 00000000..51bf2695 --- /dev/null +++ b/service/src/main/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StaticAwsCredentialsFactory diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/LocalWhisperServerService.java b/service/src/test/java/org/whispersystems/textsecuregcm/LocalWhisperServerService.java new file mode 100644 index 00000000..ebf83d86 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/LocalWhisperServerService.java @@ -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: + *

+ * ./mvnw clean integration-test -DskipTests=true -Ptest-server + *

+ * NOTE: many features are non-functional, especially those that depend on external services + */ +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()); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/WhisperServerServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/WhisperServerServiceTest.java new file mode 100644 index 00000000..e816a326 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/WhisperServerServiceTest.java @@ -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 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()); + } + + } + +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalDynamoDbFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalDynamoDbFactory.java new file mode 100644 index 00000000..3cfeae1d --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalDynamoDbFactory.java @@ -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(); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalFaultTolerantRedisClusterFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalFaultTolerantRedisClusterFactory.java new file mode 100644 index 00000000..f9e181ca --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalFaultTolerantRedisClusterFactory.java @@ -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); + } + +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalSingletonRedisClientFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalSingletonRedisClientFactory.java new file mode 100644 index 00000000..c17e2706 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalSingletonRedisClientFactory.java @@ -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); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/NoWaitDogstatsdConfiguration.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/NoWaitDogstatsdConfiguration.java new file mode 100644 index 00000000..fcdae446 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/NoWaitDogstatsdConfiguration.java @@ -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; + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticDynamicConfigurationManagerFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticDynamicConfigurationManagerFactory.java new file mode 100644 index 00000000..d01f49bf --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticDynamicConfigurationManagerFactory.java @@ -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 DynamicConfigurationManager build(final Class klazz, + final ScheduledExecutorService scheduledExecutorService, final AwsCredentialsProvider awsCredentialsProvider) { + + return new StaticDynamicConfigurationManager<>(staticConfig, application, environment, configuration, + awsCredentialsProvider, klazz, scheduledExecutorService); + } + + private static class StaticDynamicConfigurationManager extends DynamicConfigurationManager { + + private final T configuration; + + public StaticDynamicConfigurationManager(final String config, final String application, final String environment, + final String configurationName, final AwsCredentialsProvider awsCredentialsProvider, + final Class 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 + } + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticS3ObjectMonitorFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticS3ObjectMonitorFactory.java new file mode 100644 index 00000000..de2a4e32 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticS3ObjectMonitorFactory.java @@ -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 changeListener) { + changeListener.accept(new ByteArrayInputStream(object)); + } + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubHCaptchaClientFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubHCaptchaClientFactory.java new file mode 100644 index 00000000..d68f42b7 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubHCaptchaClientFactory.java @@ -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 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 dynamicConfigurationManager) { + super(null, retryExecutor, circuitBreakerConfiguration, null, dynamicConfigurationManager); + } + + @Override + public String scheme() { + return "test"; + } + + @Override + public Set 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(); + } + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubPaymentsServiceClientsFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubPaymentsServiceClientsFactory.java new file mode 100644 index 00000000..28d1423b --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubPaymentsServiceClientsFactory.java @@ -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 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; + } + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubPubSubPublisherFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubPubSubPublisherFactory.java new file mode 100644 index 00000000..27cabfbd --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubPubSubPublisherFactory.java @@ -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()); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubRegistrationServiceClientFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubRegistrationServiceClientFactory.java new file mode 100644 index 00000000..bdb73cd5 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StubRegistrationServiceClientFactory.java @@ -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 SESSIONS = new ConcurrentHashMap<>(); + + public StubRegistrationServiceClient(final String registrationCaCertificate) throws IOException { + super("example.com", 8080, null, registrationCaCertificate, null); + } + + @Override + public CompletableFuture 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 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 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> getSession(final byte[] sessionId, + final Duration timeout) { + return CompletableFuture.completedFuture( + Optional.ofNullable(SESSIONS.get(Base64.getEncoder().encodeToString(sessionId)))); + } + } + +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/push/ProvisioningManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/push/ProvisioningManagerTest.java index 30067d7d..72e1e8cc 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/push/ProvisioningManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/push/ProvisioningManagerTest.java @@ -8,7 +8,6 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import com.google.protobuf.ByteString; -import java.time.Duration; import java.util.function.Consumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -32,7 +31,7 @@ class ProvisioningManagerTest { @BeforeEach 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(); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/redis/RedisSingletonExtension.java b/service/src/test/java/org/whispersystems/textsecuregcm/redis/RedisSingletonExtension.java index cf1b28eb..eb8d4ed3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/redis/RedisSingletonExtension.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/redis/RedisSingletonExtension.java @@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.redis; import static org.junit.jupiter.api.Assumptions.assumeFalse; import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; import java.io.IOException; import java.net.ServerSocket; @@ -22,6 +23,7 @@ public class RedisSingletonExtension implements BeforeAllCallback, BeforeEachCal private static RedisServer redisServer; private RedisClient redisClient; + private RedisURI redisUri; public static class RedisSingletonExtensionBuilder { @@ -53,7 +55,8 @@ public class RedisSingletonExtension implements BeforeAllCallback, BeforeEachCal @Override 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 connection = redisClient.connect()) { connection.sync().flushall(); @@ -76,6 +79,10 @@ public class RedisSingletonExtension implements BeforeAllCallback, BeforeEachCal return redisClient; } + public RedisURI getRedisUri() { + return redisUri; + } + private static int getAvailablePort() throws IOException { try (ServerSocket socket = new ServerSocket(0)) { socket.setReuseAddress(false); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitorTest.java index cea5b33c..c20427ac 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/s3/S3ObjectMonitorTest.java @@ -46,8 +46,7 @@ class S3ObjectMonitorTest { objectKey, 16 * 1024 * 1024, mock(ScheduledExecutorService.class), - Duration.ofMinutes(1), - listener); + Duration.ofMinutes(1)); final String uuid = UUID.randomUUID().toString(); when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn( @@ -55,8 +54,8 @@ class S3ObjectMonitorTest { final ResponseInputStream ris = responseInputStreamFromString("abc", uuid); when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris); - objectMonitor.refresh(); - objectMonitor.refresh(); + objectMonitor.refresh(listener); + objectMonitor.refresh(listener); verify(listener).accept(ris); } @@ -77,8 +76,7 @@ class S3ObjectMonitorTest { objectKey, 16 * 1024 * 1024, mock(ScheduledExecutorService.class), - Duration.ofMinutes(1), - listener); + Duration.ofMinutes(1)); final String uuid = UUID.randomUUID().toString(); 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); objectMonitor.getObject(); - objectMonitor.refresh(); + objectMonitor.refresh(listener); verify(listener, never()).accept(responseInputStream); } @@ -115,8 +113,7 @@ class S3ObjectMonitorTest { objectKey, maxObjectSize, mock(ScheduledExecutorService.class), - Duration.ofMinutes(1), - listener); + Duration.ofMinutes(1)); final String uuid = UUID.randomUUID().toString(); when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn( @@ -124,7 +121,7 @@ class S3ObjectMonitorTest { final ResponseInputStream ris = responseInputStreamFromString("a".repeat((int) maxObjectSize+1), uuid); when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris); - objectMonitor.refresh(); + objectMonitor.refresh(listener); verify(listener, never()).accept(any()); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java index bb35b5ec..4f2c7170 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java @@ -11,6 +11,7 @@ import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; import java.net.ServerSocket; import java.net.URI; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.extension.AfterEachCallback; 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.LocalSecondaryIndex; import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import javax.annotation.Nullable; public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback { + private static final String DEFAULT_LIBRARY_PATH = "target/lib"; + public interface TableSchema { String tableName(); String hashKeyName(); @@ -58,22 +62,28 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback private DynamoDBProxyServer server; private int port; + private final String libraryPath; private final List schemas; private DynamoDbClient dynamoDB2; private DynamoDbAsyncClient dynamoAsyncDB2; 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); } - 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 if (libraryLoaded.get()) { return; } 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 - SQLite.setLibraryPath("target/lib"); + SQLite.setLibraryPath(this.libraryPath); } } diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DatadogConfiguration b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DatadogConfiguration new file mode 100644 index 00000000..312e8e76 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DatadogConfiguration @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.NoWaitDogstatsdConfiguration diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory new file mode 100644 index 00000000..9bc8ddf4 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StaticDynamicConfigurationManagerFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory new file mode 100644 index 00000000..a412db4f --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.LocalDynamoDbFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory new file mode 100644 index 00000000..8b83b743 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.LocalFaultTolerantRedisClusterFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.HCaptchaClientFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.HCaptchaClientFactory new file mode 100644 index 00000000..17d4ad3d --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.HCaptchaClientFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StubHCaptchaClientFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.PaymentsServiceClientsFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.PaymentsServiceClientsFactory new file mode 100644 index 00000000..77d25c67 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.PaymentsServiceClientsFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StubPaymentsServiceClientsFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.PubSubPublisherFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.PubSubPublisherFactory new file mode 100644 index 00000000..f46ac108 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.PubSubPublisherFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StubPubSubPublisherFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory new file mode 100644 index 00000000..3813423b --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StubRegistrationServiceClientFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory new file mode 100644 index 00000000..05844241 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.StaticS3ObjectMonitorFactory diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.SingletonRedisClientFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.SingletonRedisClientFactory new file mode 100644 index 00000000..f851b937 --- /dev/null +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.SingletonRedisClientFactory @@ -0,0 +1 @@ +org.whispersystems.textsecuregcm.configuration.LocalSingletonRedisClientFactory diff --git a/service/src/test/resources/config/test-secrets-bundle.yml b/service/src/test/resources/config/test-secrets-bundle.yml new file mode 100644 index 00000000..9299d449 --- /dev/null +++ b/service/src/test/resources/config/test-secrets-bundle.yml @@ -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 diff --git a/service/src/test/resources/config/test.yml b/service/src/test/resources/config/test.yml new file mode 100644 index 00000000..5a945b6f --- /dev/null +++ b/service/src/test/resources/config/test.yml @@ -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