From 225ef2917adad4f1431935c38b6e260a0c8e6fc2 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 9 May 2020 19:43:58 -0700 Subject: [PATCH] Support downloading and deleting S3 backups --- .../Servers/DownloadBackupController.php | 87 ++++++++++++++++--- app/Services/Backups/DeleteBackupService.php | 31 +++++++ 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php b/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php index 4acb0c9c4..1d22b7c35 100644 --- a/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php @@ -3,12 +3,16 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Carbon\CarbonImmutable; +use Pterodactyl\Models\User; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; +use Illuminate\Http\JsonResponse; use Pterodactyl\Services\Nodes\NodeJWTService; use Illuminate\Contracts\Routing\ResponseFactory; +use Pterodactyl\Extensions\Backups\BackupManager; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DownloadBackupRequest; class DownloadBackupController extends ClientApiController @@ -28,16 +32,23 @@ class DownloadBackupController extends ClientApiController */ private $jwtService; + /** + * @var \Pterodactyl\Extensions\Backups\BackupManager + */ + private $backupManager; + /** * DownloadBackupController constructor. * * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository * @param \Pterodactyl\Services\Nodes\NodeJWTService $jwtService + * @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager * @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory */ public function __construct( DaemonBackupRepository $daemonBackupRepository, NodeJWTService $jwtService, + BackupManager $backupManager, ResponseFactory $responseFactory ) { parent::__construct(); @@ -45,6 +56,7 @@ class DownloadBackupController extends ClientApiController $this->daemonBackupRepository = $daemonBackupRepository; $this->responseFactory = $responseFactory; $this->jwtService = $jwtService; + $this->backupManager = $backupManager; } /** @@ -55,9 +67,65 @@ class DownloadBackupController extends ClientApiController * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DownloadBackupRequest $request * @param \Pterodactyl\Models\Server $server * @param \Pterodactyl\Models\Backup $backup - * @return array + * @return \Illuminate\Http\JsonResponse */ public function __invoke(DownloadBackupRequest $request, Server $server, Backup $backup) + { + switch ($backup->disk) { + case Backup::ADAPTER_WINGS: + $url = $this->getLocalBackupUrl($backup, $server, $request->user()); + break; + case Backup::ADAPTER_AWS_S3: + $url = $this->getS3BackupUrl($backup, $server); + break; + default: + throw new BadRequestHttpException; + } + + return JsonResponse::create([ + 'object' => 'signed_url', + 'attributes' => [ + 'url' => $url, + ], + ]); + } + + /** + * Returns a signed URL that allows us to download a file directly out of a non-public + * S3 bucket by using a signed URL. + * + * @param \Pterodactyl\Models\Backup $backup + * @param \Pterodactyl\Models\Server $server + * @return string + */ + protected function getS3BackupUrl(Backup $backup, Server $server) + { + /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + $adapter = $this->backupManager->adapter(Backup::ADAPTER_AWS_S3); + + $client = $adapter->getClient(); + + $request = $client->createPresignedRequest( + $client->getCommand('GetObject', [ + 'Bucket' => $adapter->getBucket(), + 'Key' => sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid), + 'ContentType' => 'application/x-gzip', + ]), + CarbonImmutable::now()->addMinutes(5) + ); + + return $request->getUri()->__toString(); + } + + /** + * Returns a download link a backup stored on a wings instance. + * + * @param \Pterodactyl\Models\Backup $backup + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User $user + * @return string + */ + protected function getLocalBackupUrl(Backup $backup, Server $server, User $user) { $token = $this->jwtService ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) @@ -65,17 +133,12 @@ class DownloadBackupController extends ClientApiController 'backup_uuid' => $backup->uuid, 'server_uuid' => $server->uuid, ]) - ->handle($server->node, $request->user()->id . $server->uuid); + ->handle($server->node, $user->id . $server->uuid); - return [ - 'object' => 'signed_url', - 'attributes' => [ - 'url' => sprintf( - '%s/download/backup?token=%s', - $server->node->getConnectionAddress(), - $token->__toString() - ), - ], - ]; + return sprintf( + '%s/download/backup?token=%s', + $server->node->getConnectionAddress(), + $token->__toString() + ); } } diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php index 15fd02a97..094b636cf 100644 --- a/app/Services/Backups/DeleteBackupService.php +++ b/app/Services/Backups/DeleteBackupService.php @@ -6,6 +6,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Backup; use GuzzleHttp\Exception\ClientException; use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Extensions\Backups\BackupManager; use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; @@ -27,21 +28,29 @@ class DeleteBackupService */ private $connection; + /** + * @var \Pterodactyl\Extensions\Backups\BackupManager + */ + private $manager; + /** * DeleteBackupService constructor. * * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository + * @param \Pterodactyl\Extensions\Backups\BackupManager $manager * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository */ public function __construct( ConnectionInterface $connection, BackupRepository $repository, + BackupManager $manager, DaemonBackupRepository $daemonBackupRepository ) { $this->repository = $repository; $this->daemonBackupRepository = $daemonBackupRepository; $this->connection = $connection; + $this->manager = $manager; } /** @@ -52,6 +61,12 @@ class DeleteBackupService */ public function handle(Backup $backup) { + if ($backup->disk === Backup::ADAPTER_AWS_S3) { + $this->deleteFromS3($backup); + + return; + } + $this->connection->transaction(function () use ($backup) { try { $this->daemonBackupRepository->setServer($backup->server)->delete($backup); @@ -67,4 +82,20 @@ class DeleteBackupService $this->repository->delete($backup->id); }); } + + /** + * Deletes a backup from an S3 disk. + * + * @param \Pterodactyl\Models\Backup $backup + */ + protected function deleteFromS3(Backup $backup) + { + /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + $adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3); + + $adapter->getClient()->deleteObject([ + 'Bucket' => $adapter->getBucket(), + 'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid), + ]); + } }