Skip to content

Commit

Permalink
Optimize storage and speed when making an encrypted backup
Browse files Browse the repository at this point in the history
  • Loading branch information
pellaras committed Oct 7, 2024
1 parent 5b67271 commit a711fef
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 180 deletions.
5 changes: 0 additions & 5 deletions docs/advanced-usage/encrypt-backup-archives.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ By default you only have to define the `BACKUP_ARCHIVE_PASSWORD` environment var

If you want to customize this you can configure the `backup.backup.password` and `backup.backup.encryption` keys in your `config/backup.php` file.

The whole encryption is done with an event listener.
The `\Spatie\Backup\Listeners\EncryptBackupArchive` listener is attached to the `\Spatie\Backup\Events\BackupZipWasCreated` event.
The listener is added to the event when both required config keys are not `null`.
You are free to add this listener your own or override it.

It's important to try this workflow and also to decrypt a backup archive.
So you know that it works and you have a working backup restore solution.

Expand Down
4 changes: 0 additions & 4 deletions src/BackupServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ public function configurePackage(Package $package): void
public function packageBooted()
{
$this->app['events']->subscribe(EventHandler::class);

if (EncryptBackupArchive::shouldEncrypt()) {
Event::listen(BackupZipWasCreated::class, EncryptBackupArchive::class);
}
}

public function packageRegistered()
Expand Down
67 changes: 0 additions & 67 deletions src/Listeners/EncryptBackupArchive.php

This file was deleted.

6 changes: 1 addition & 5 deletions src/Tasks/Backup/BackupJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,7 @@ protected function createZipContainingEveryFileInManifest(Manifest $manifest): s

consoleOutput()->info("Created zip containing {$zip->count()} files and directories. Size is {$zip->humanReadableSize()}");

if ($this->sendNotifications) {
$this->sendNotification(new BackupZipWasCreated($pathToZip));
} else {
app()->call('\Spatie\Backup\Listeners\EncryptBackupArchive@handle', ['event' => new BackupZipWasCreated($pathToZip)]);
}
$this->sendNotification(new BackupZipWasCreated($pathToZip));

return $pathToZip;
}
Expand Down
27 changes: 27 additions & 0 deletions src/Tasks/Backup/Encryption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Spatie\Backup\Tasks\Backup;

class Encryption
{
protected string $password;

protected int $method;

public function __construct(string $password, int $method)
{
$this->password = $password;

$this->method = $method;
}

public function getPassword(): string
{
return $this->password;
}

public function getMethod(): int
{
return $this->method;
}
}
49 changes: 49 additions & 0 deletions src/Tasks/Backup/Zip.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class Zip

protected string $pathToZip;

protected ?Encryption $encryption = null;

public static function createForManifest(Manifest $manifest, string $pathToZip): self
{
$relativePath = config('backup.backup.source.files.relative_path') ?
Expand All @@ -27,6 +29,8 @@ public static function createForManifest(Manifest $manifest, string $pathToZip):
$zip->add($file, self::determineNameOfFileInZip($file, $pathToZip, $relativePath));
}

$zip->encrypt();

$zip->close();

return $zip;
Expand Down Expand Up @@ -87,6 +91,11 @@ public function close(): void
$this->zipFile->close();
}

public function setPassword(string $password): void
{
$this->zipFile->setPassword($password);
}

public function add(string|iterable $files, ?string $nameInZip = null): self
{
if (is_array($files)) {
Expand Down Expand Up @@ -126,4 +135,44 @@ public function count(): int
{
return $this->fileCount;
}

public function encrypt()
{
$this->loadEncryption();

if ($this->getEncryption()) {
$this->setPassword($this->getEncryption()->getPassword());

foreach (range(0, $this->zipFile->numFiles - 1) as $i) {
$this->zipFile->setEncryptionIndex($i, $this->getEncryption()->getMethod());
}
}
}

public function getEncryption(): ?Encryption
{
return $this->encryption;
}

public function loadEncryption()
{
$password = config('backup.backup.password');
$method = config('backup.backup.encryption');

if ($method === 'default') {
$method = defined("\ZipArchive::EM_AES_256")
? ZipArchive::EM_AES_256
: null;
}

if ($password === null) {
return false;
}

if (! is_int($method)) {
return false;
}

$this->encryption = new Encryption($password, $method);
}
}
68 changes: 66 additions & 2 deletions tests/Commands/BackupCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@

$testFiles = [
'.dotfile',
'archive.zip',
'1Mb.file',
'directory1/',
'directory1/directory1/',
Expand Down Expand Up @@ -358,8 +357,73 @@
app()['db']->disconnect();
});

it('keeps archive unencrypted without password', function () {
config()->set('backup.backup.password', null);
config()->set('backup.backup.source.files.include', [$this->getStubDirectory()]);
config()->set('backup.backup.source.files.relative_path', $this->getStubDirectory());

$this->artisan('backup:run --only-files')->assertExitCode(0);
Storage::disk('local')->assertExists($this->expectedZipPath);

$zip = new ZipArchive();
$zip->open(Storage::disk('local')->path($this->expectedZipPath));

$this->assertEncryptionMethod($zip, ZipArchive::EM_NONE);

$this->assertTrue($zip->extractTo(Storage::disk('local')->path('temp/extraction')));
$this->assertValidExtractedFiles();

$zip->close();
});

/**
* @param int $algorithm
*/
it('encrypts archive with password', function (int $algorithm) {
config()->set('backup.backup.password', $this->fakePassword());
config()->set('backup.backup.encryption', $algorithm);
config()->set('backup.backup.source.files.include', [$this->getStubDirectory()]);
config()->set('backup.backup.source.files.relative_path', $this->getStubDirectory());

$this->artisan('backup:run --only-files')->assertExitCode(0);

Storage::disk('local')->assertExists($this->expectedZipPath);

$zip = new ZipArchive();
$zip->open(Storage::disk('local')->path($this->expectedZipPath));

$this->assertEncryptionMethod($zip, $algorithm);

$zip->setPassword($this->fakePassword());
$this->assertTrue($zip->extractTo(Storage::disk('local')->path('temp/extraction')));
$this->assertValidExtractedFiles();

$zip->close();
})->with([
[ZipArchive::EM_AES_128],
[ZipArchive::EM_AES_192],
[ZipArchive::EM_AES_256],
]);

it('can not open encrypted archive without password', function () {
config()->set('backup.backup.password', $this->fakePassword());

$this->artisan('backup:run --only-files')->assertExitCode(0);

Storage::disk('local')->assertExists($this->expectedZipPath);

$zip = new ZipArchive();
$zip->open(Storage::disk('local')->path($this->expectedZipPath));

$this->assertEncryptionMethod($zip, ZipArchive::EM_AES_256);

expect($zip->extractTo(Storage::disk('local')->path('temp/extraction')))->toBeFalse();

$zip->close();
});

it('will encrypt backup when notifications are disabled', function () {
config()->set('backup.backup.password', '24dsjF6BPjWgUfTu');
config()->set('backup.backup.password', $this->fakePassword());
config()->set('backup.backup.source.databases', ['db1']);

$this->artisan('backup:run --disable-notifications --only-db --db-name=db1 --only-to-disk=local')->assertExitCode(0);
Expand Down
4 changes: 0 additions & 4 deletions tests/FileSelectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
'.dot',
'.dot/file1.txt',
'.dotfile',
'archive.zip',
'1Mb.file',
'directory1',
'directory1/directory1',
Expand Down Expand Up @@ -43,7 +42,6 @@
'.dot',
'.dot/file1.txt',
'.dotfile',
'archive.zip',
'1Mb.file',
'directory2',
'directory2/directory1',
Expand All @@ -69,7 +67,6 @@
$testFiles = getTestFiles([
'.dot',
'.dotfile',
'archive.zip',
'1Mb.file',
'directory1',
'directory1/file2.txt',
Expand Down Expand Up @@ -113,7 +110,6 @@
'.dot',
'.dot/file1.txt',
'.dotfile',
'archive.zip',
'1Mb.file',
'directory1',
'directory1/file1.txt',
Expand Down
Loading

0 comments on commit a711fef

Please sign in to comment.