mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 20:18:05 +01:00
Add support to trial Cloudflare TURN beta
This commit is contained in:
@@ -57,7 +57,7 @@ import org.whispersystems.textsecuregcm.configuration.SpamFilterConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.StripeConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TlsKeyStoreConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnSecretConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.VirtualThreadConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
|
||||
@@ -288,7 +288,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private TurnSecretConfiguration turn;
|
||||
private TurnConfiguration turn;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@@ -529,7 +529,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return registrationService;
|
||||
}
|
||||
|
||||
public TurnSecretConfiguration getTurnSecretConfiguration() {
|
||||
public TurnConfiguration getTurnConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
|
||||
@@ -605,7 +605,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
pushLatencyManager);
|
||||
final ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender, receiptSenderExecutor);
|
||||
final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(dynamicConfigurationManager,
|
||||
config.getTurnSecretConfiguration().secret().value());
|
||||
config.getTurnConfiguration().secret().value(), config.getTurnConfiguration().cloudflare());
|
||||
|
||||
final CardinalityEstimator messageByteLimitCardinalityEstimator = new CardinalityEstimator(
|
||||
rateLimitersCluster,
|
||||
@@ -938,7 +938,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
new AttachmentControllerV4(rateLimiters, gcsAttachmentGenerator, tusAttachmentGenerator,
|
||||
experimentEnrollmentManager),
|
||||
new ArchiveController(backupAuthManager, backupManager),
|
||||
new CallRoutingController(rateLimiters, callRouter, turnTokenGenerator),
|
||||
new CallRoutingController(rateLimiters, callRouter, turnTokenGenerator, experimentEnrollmentManager),
|
||||
new CallLinkController(rateLimiters, callingGenericZkSecretParams),
|
||||
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().certificate().value(),
|
||||
config.getDeliveryCertificate().ecPrivateKey(), config.getDeliveryCertificate().expiresDays()),
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public record TurnToken(String username, String password, List<String> urls, List<String> urlsWithIps, String hostname) {
|
||||
public record TurnToken(String username, String password, List<String> urls, @Nullable List<String> urlsWithIps,
|
||||
@Nullable String hostname) {
|
||||
public TurnToken(String username, String password, List<String> urls) {
|
||||
this(username, password, urls, null, null);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,6 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnUriConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicTurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import org.whispersystems.textsecuregcm.util.WeightedRandomSelect;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -25,6 +14,17 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
|
||||
import org.whispersystems.textsecuregcm.configuration.CloudflareTurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnUriConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicTurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import org.whispersystems.textsecuregcm.util.WeightedRandomSelect;
|
||||
|
||||
public class TurnTokenGenerator {
|
||||
|
||||
@@ -38,13 +38,22 @@ public class TurnTokenGenerator {
|
||||
|
||||
private static final String WithIpsProtocol = "01";
|
||||
|
||||
private final String cloudflareTurnUsername;
|
||||
private final String cloudflareTurnPassword;
|
||||
private final List<String> cloudflareTurnUrls;
|
||||
|
||||
public TurnTokenGenerator(final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
|
||||
final byte[] turnSecret) {
|
||||
final byte[] turnSecret, final CloudflareTurnConfiguration cloudflareTurnConfiguration) {
|
||||
|
||||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||
this.turnSecret = turnSecret;
|
||||
|
||||
this.cloudflareTurnUsername = cloudflareTurnConfiguration.username().value();
|
||||
this.cloudflareTurnPassword = cloudflareTurnConfiguration.password().value();
|
||||
this.cloudflareTurnUrls = cloudflareTurnConfiguration.urls();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public TurnToken generate(final UUID aci) {
|
||||
return generateToken(null, null, urls(aci));
|
||||
}
|
||||
@@ -53,6 +62,10 @@ public class TurnTokenGenerator {
|
||||
return generateToken(options.hostname(), options.urlsWithIps(), options.urlsWithHostname());
|
||||
}
|
||||
|
||||
public TurnToken generateForCloudflareBeta() {
|
||||
return new TurnToken(cloudflareTurnUsername, cloudflareTurnPassword, cloudflareTurnUrls);
|
||||
}
|
||||
|
||||
private TurnToken generateToken(String hostname, List<String> urlsWithIps, List<String> urlsWithHostname) {
|
||||
try {
|
||||
final Mac mac = Mac.getInstance(ALGORITHM);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public record CloudflareTurnConfiguration(@NotNull SecretString username, @NotNull SecretString password,
|
||||
@Valid @NotNull List<@NotBlank String> urls) {
|
||||
|
||||
}
|
||||
@@ -7,5 +7,5 @@ package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
|
||||
|
||||
public record TurnSecretConfiguration(SecretBytes secret) {
|
||||
public record TurnConfiguration(SecretBytes secret, CloudflareTurnConfiguration cloudflare) {
|
||||
}
|
||||
@@ -95,7 +95,8 @@ public class AccountController {
|
||||
this.usernameHashZkProofVerifier = usernameHashZkProofVerifier;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
// may be removed after 2024-07-16
|
||||
@Deprecated(forRemoval = true)
|
||||
@GET
|
||||
@Path("/turn/")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Optional;
|
||||
@@ -21,14 +22,13 @@ import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnToken;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.websocket.auth.ReadOnly;
|
||||
|
||||
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||
|
||||
@Path("/v1/calling")
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Calling")
|
||||
public class CallRoutingController {
|
||||
@@ -39,15 +39,18 @@ public class CallRoutingController {
|
||||
private final RateLimiters rateLimiters;
|
||||
private final TurnCallRouter turnCallRouter;
|
||||
private final TurnTokenGenerator tokenGenerator;
|
||||
private final ExperimentEnrollmentManager experimentEnrollmentManager;
|
||||
|
||||
public CallRoutingController(
|
||||
final RateLimiters rateLimiters,
|
||||
final TurnCallRouter turnCallRouter,
|
||||
final TurnTokenGenerator tokenGenerator
|
||||
final TurnTokenGenerator tokenGenerator,
|
||||
final ExperimentEnrollmentManager experimentEnrollmentManager
|
||||
) {
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.turnCallRouter = turnCallRouter;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
this.experimentEnrollmentManager = experimentEnrollmentManager;
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -63,7 +66,7 @@ public class CallRoutingController {
|
||||
@ApiResponse(responseCode = "400", description = "Invalid get call endpoint request.")
|
||||
@ApiResponse(responseCode = "401", description = "Account authentication check failed.")
|
||||
@ApiResponse(responseCode = "422", description = "Invalid request format.")
|
||||
@ApiResponse(responseCode = "429", description = "Ratelimited.")
|
||||
@ApiResponse(responseCode = "429", description = "Rate limited.")
|
||||
public TurnToken getCallingRelays(
|
||||
final @ReadOnly @Auth AuthenticatedAccount auth,
|
||||
@Context ContainerRequestContext requestContext
|
||||
@@ -71,6 +74,10 @@ public class CallRoutingController {
|
||||
UUID aci = auth.getAccount().getUuid();
|
||||
rateLimiters.getCallEndpointLimiter().validate(aci);
|
||||
|
||||
if (experimentEnrollmentManager.isEnrolled(aci, "cloudflareTurn")) {
|
||||
return tokenGenerator.generateForCloudflareBeta();
|
||||
}
|
||||
|
||||
Optional<InetAddress> address = Optional.empty();
|
||||
try {
|
||||
final String remoteAddress = (String) requestContext.getProperty(
|
||||
|
||||
Reference in New Issue
Block a user