|
8 | 8 | import com.google.common.annotations.VisibleForTesting;
|
9 | 9 | import io.dropwizard.util.DataSize;
|
10 | 10 | import io.grpc.Status;
|
11 |
| -import io.grpc.StatusRuntimeException; |
12 | 11 | import io.micrometer.core.instrument.DistributionSummary;
|
13 | 12 | import io.micrometer.core.instrument.Metrics;
|
14 | 13 | import io.micrometer.core.instrument.Tag;
|
|
19 | 18 | import java.time.Duration;
|
20 | 19 | import java.time.Instant;
|
21 | 20 | import java.util.Base64;
|
| 21 | +import java.util.HexFormat; |
22 | 22 | import java.util.List;
|
23 | 23 | import java.util.Map;
|
24 | 24 | import java.util.Optional;
|
|
38 | 38 | import org.whispersystems.textsecuregcm.attachments.AttachmentGenerator;
|
39 | 39 | import org.whispersystems.textsecuregcm.attachments.TusAttachmentGenerator;
|
40 | 40 | import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
| 41 | +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; |
| 42 | +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; |
41 | 43 | import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
42 | 44 | import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
|
43 | 45 | import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
| 46 | +import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecoveryClient; |
44 | 47 | import org.whispersystems.textsecuregcm.util.AsyncTimerUtil;
|
45 | 48 | import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
46 | 49 | import org.whispersystems.textsecuregcm.util.Pair;
|
@@ -94,24 +97,29 @@ public class BackupManager {
|
94 | 97 | private final Cdn3BackupCredentialGenerator cdn3BackupCredentialGenerator;
|
95 | 98 | private final RemoteStorageManager remoteStorageManager;
|
96 | 99 | private final SecureRandom secureRandom = new SecureRandom();
|
| 100 | + private final ExternalServiceCredentialsGenerator secureValueRecoveryBCredentialsGenerator; |
| 101 | + private final SecureValueRecoveryClient secureValueRecoveryBClient; |
97 | 102 | private final Clock clock;
|
98 | 103 |
|
99 |
| - |
100 | 104 | public BackupManager(
|
101 | 105 | final BackupsDb backupsDb,
|
102 | 106 | final GenericServerSecretParams serverSecretParams,
|
103 | 107 | final RateLimiters rateLimiters,
|
104 | 108 | final TusAttachmentGenerator tusAttachmentGenerator,
|
105 | 109 | final Cdn3BackupCredentialGenerator cdn3BackupCredentialGenerator,
|
106 | 110 | final RemoteStorageManager remoteStorageManager,
|
| 111 | + final ExternalServiceCredentialsGenerator secureValueRecoveryBCredentialsGenerator, |
| 112 | + final SecureValueRecoveryClient secureValueRecoveryBClient, |
107 | 113 | final Clock clock) {
|
108 | 114 | this.backupsDb = backupsDb;
|
109 | 115 | this.serverSecretParams = serverSecretParams;
|
110 | 116 | this.rateLimiters = rateLimiters;
|
111 | 117 | this.tusAttachmentGenerator = tusAttachmentGenerator;
|
112 | 118 | this.cdn3BackupCredentialGenerator = cdn3BackupCredentialGenerator;
|
113 | 119 | this.remoteStorageManager = remoteStorageManager;
|
| 120 | + this.secureValueRecoveryBClient = secureValueRecoveryBClient; |
114 | 121 | this.clock = clock;
|
| 122 | + this.secureValueRecoveryBCredentialsGenerator = secureValueRecoveryBCredentialsGenerator; |
115 | 123 | }
|
116 | 124 |
|
117 | 125 |
|
@@ -387,6 +395,26 @@ public Map<String, String> generateReadAuth(final AuthenticatedBackupUser backup
|
387 | 395 | return cdn3BackupCredentialGenerator.readHeaders(backupUser.backupDir());
|
388 | 396 | }
|
389 | 397 |
|
| 398 | + /** |
| 399 | + * Generate credentials that can be used with SVRB |
| 400 | + * |
| 401 | + * @param backupUser an already ZK authenticated backup user |
| 402 | + * @return the credential that may be used with SVRB |
| 403 | + */ |
| 404 | + public ExternalServiceCredentials generateSvrbAuth(final AuthenticatedBackupUser backupUser) { |
| 405 | + checkBackupLevel(backupUser, BackupLevel.FREE); |
| 406 | + // Clients may only use SVRB with their messages backup-id |
| 407 | + checkBackupCredentialType(backupUser, BackupCredentialType.MESSAGES); |
| 408 | + return secureValueRecoveryBCredentialsGenerator.generateFor(svrbIdentifier(backupUser)); |
| 409 | + } |
| 410 | + |
| 411 | + private static String svrbIdentifier(final AuthenticatedBackupUser backupUser) { |
| 412 | + return svrbIdentifier(BackupsDb.hashedBackupId(backupUser.backupId())); |
| 413 | + } |
| 414 | + |
| 415 | + private static String svrbIdentifier(final byte[] hashedBackupId) { |
| 416 | + return HexFormat.of().formatHex(hashedBackupId); |
| 417 | + } |
390 | 418 |
|
391 | 419 | /**
|
392 | 420 | * List of media stored for a particular backup id
|
@@ -427,13 +455,19 @@ public CompletionStage<ListMediaResult> list(
|
427 | 455 |
|
428 | 456 | public CompletableFuture<Void> deleteEntireBackup(final AuthenticatedBackupUser backupUser) {
|
429 | 457 | checkBackupLevel(backupUser, BackupLevel.FREE);
|
430 |
| - return backupsDb |
| 458 | + |
| 459 | + // Clients only include SVRB data with their messages backup-id |
| 460 | + final CompletableFuture<Void> svrbRemoval = switch(backupUser.credentialType()) { |
| 461 | + case BackupCredentialType.MESSAGES -> secureValueRecoveryBClient.removeData(svrbIdentifier(backupUser)); |
| 462 | + case BackupCredentialType.MEDIA -> CompletableFuture.completedFuture(null); |
| 463 | + }; |
| 464 | + return svrbRemoval.thenCompose(_ -> backupsDb |
431 | 465 | // Try to swap out the backupDir for the user
|
432 | 466 | .scheduleBackupDeletion(backupUser)
|
433 | 467 | // If there was already a pending swap, try to delete the cdn objects directly
|
434 | 468 | .exceptionallyCompose(ExceptionUtils.exceptionallyHandler(BackupsDb.PendingDeletionException.class, e ->
|
435 | 469 | AsyncTimerUtil.record(SYNCHRONOUS_DELETE_TIMER, () ->
|
436 |
| - deletePrefix(backupUser.backupDir(), DELETION_CONCURRENCY)))); |
| 470 | + deletePrefix(backupUser.backupDir(), DELETION_CONCURRENCY))))); |
437 | 471 | }
|
438 | 472 |
|
439 | 473 |
|
@@ -617,12 +651,17 @@ public Flux<ExpiredBackup> getExpiredBackups(final int segments, final Scheduler
|
617 | 651 | * @return A stage that completes when the deletion operation is finished
|
618 | 652 | */
|
619 | 653 | public CompletableFuture<Void> expireBackup(final ExpiredBackup expiredBackup) {
|
620 |
| - return backupsDb.startExpiration(expiredBackup) |
| 654 | + // Clients only include SVRB data with their messages backup-id |
| 655 | + final CompletableFuture<Void> svrbRemoval = switch(expiredBackup.expirationType()) { |
| 656 | + case ALL -> secureValueRecoveryBClient.removeData(svrbIdentifier(expiredBackup.hashedBackupId())); |
| 657 | + case MEDIA, GARBAGE_COLLECTION -> CompletableFuture.completedFuture(null); |
| 658 | + }; |
| 659 | + return svrbRemoval.thenCompose(_ -> backupsDb.startExpiration(expiredBackup) |
621 | 660 | // the deletion operation is effectively single threaded -- it's expected that the caller can increase
|
622 | 661 | // concurrency by deleting more backups at once, rather than increasing concurrency deleting an individual
|
623 | 662 | // backup
|
624 | 663 | .thenCompose(ignored -> deletePrefix(expiredBackup.prefixToDelete(), 1))
|
625 |
| - .thenCompose(ignored -> backupsDb.finishExpiration(expiredBackup)); |
| 664 | + .thenCompose(ignored -> backupsDb.finishExpiration(expiredBackup))); |
626 | 665 | }
|
627 | 666 |
|
628 | 667 | /**
|
|
0 commit comments