From e6dded61a437955d70fe6db5567733090d307f08 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Thu, 16 May 2024 11:53:18 +0200 Subject: [PATCH] backups: ensure requesting node is checked Co-authored-by: matthewpi --- .../Backups/BackupRemoteUploadController.php | 25 +++++++++++++++---- .../Remote/Backups/BackupStatusController.php | 17 ++++++++++++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php index e9829b3215..5c72b772f5 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php @@ -11,6 +11,7 @@ use App\Extensions\Filesystem\S3Filesystem; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use App\Exceptions\Http\HttpForbiddenException; class BackupRemoteUploadController extends Controller { @@ -32,18 +33,32 @@ public function __construct(private BackupManager $backupManager) */ public function __invoke(Request $request, string $backup): JsonResponse { + // Get the node associated with the request. + /** @var \App\Models\Node $node */ + $node = $request->attributes->get('node'); + // Get the size query parameter. $size = (int) $request->query('size'); if (empty($size)) { throw new BadRequestHttpException('A non-empty "size" query parameter must be provided.'); } - /** @var \App\Models\Backup $backup */ - $backup = Backup::query()->where('uuid', $backup)->firstOrFail(); + /** @var \App\Models\Backup $model */ + $model = Backup::query() + ->where('uuid', $backup) + ->firstOrFail(); + + // Check that the backup is "owned" by the node making the request. This avoids other nodes + // from messing with backups that they don't own. + /** @var \App\Models\Server $server */ + $server = $model->server; + if ($server->node_id !== $node->id) { + throw new HttpForbiddenException('You do not have permission to access that backup.'); + } // Prevent backups that have already been completed from trying to // be uploaded again. - if (!is_null($backup->completed_at)) { + if (!is_null($model->completed_at)) { throw new ConflictHttpException('This backup is already in a completed state.'); } @@ -54,7 +69,7 @@ public function __invoke(Request $request, string $backup): JsonResponse } // The path where backup will be uploaded to - $path = sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid); + $path = sprintf('%s/%s.tar.gz', $model->server->uuid, $model->uuid); // Get the S3 client $client = $adapter->getClient(); @@ -92,7 +107,7 @@ public function __invoke(Request $request, string $backup): JsonResponse } // Set the upload_id on the backup in the database. - $backup->update(['upload_id' => $params['UploadId']]); + $model->update(['upload_id' => $params['UploadId']]); return new JsonResponse([ 'parts' => $parts, diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index 17eb4063f0..4f32689ecf 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -13,6 +13,7 @@ use App\Extensions\Filesystem\S3Filesystem; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use App\Http\Requests\Api\Remote\ReportBackupCompleteRequest; +use App\Exceptions\Http\HttpForbiddenException; class BackupStatusController extends Controller { @@ -30,8 +31,22 @@ public function __construct(private BackupManager $backupManager) */ public function index(ReportBackupCompleteRequest $request, string $backup): JsonResponse { + // Get the node associated with the request. + /** @var \App\Models\Node $node */ + $node = $request->attributes->get('node'); + /** @var \App\Models\Backup $model */ - $model = Backup::query()->where('uuid', $backup)->firstOrFail(); + $model = Backup::query() + ->where('uuid', $backup) + ->firstOrFail(); + + // Check that the backup is "owned" by the node making the request. This avoids other nodes + // from messing with backups that they don't own. + /** @var \App\Models\Server $server */ + $server = $model->server; + if ($server->node_id !== $node->id) { + throw new HttpForbiddenException('You do not have permission to access that backup.'); + } if ($model->is_successful) { throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.');