mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Co-authored-by: Aditya Tripathi <aditya@climactic.co>
144 lines
6.3 KiB
PHP
144 lines
6.3 KiB
PHP
<?php
|
|
|
|
use App\Models\Application;
|
|
use App\Models\Environment;
|
|
use App\Models\ImageScanIgnore;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Project;
|
|
use App\Models\Server;
|
|
use App\Models\StandaloneDocker;
|
|
use App\Models\Team;
|
|
use App\Services\ImageScan\Finding;
|
|
use App\Services\ImageScan\IgnoreMatcher;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Once;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
Once::flush();
|
|
Server::flushIdentityMap();
|
|
InstanceSettings::forceCreate(['id' => 0]);
|
|
|
|
$this->team = Team::factory()->create();
|
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
|
$this->destination = StandaloneDocker::query()->where('server_id', $this->server->id)->firstOrFail();
|
|
$this->environment = Environment::factory()->create([
|
|
'project_id' => Project::factory()->create(['team_id' => $this->team->id])->id,
|
|
]);
|
|
$this->application = ignoreMatcherApplication($this->environment, $this->destination);
|
|
$this->finding = Finding::fromArray([
|
|
'id' => 'GHSA-jfh8-c2jp-5v3q', 'aliases' => ['CVE-2021-44228'], 'package' => 'log4j-core',
|
|
]);
|
|
});
|
|
|
|
function ignoreMatcherApplication(Environment $environment, StandaloneDocker $destination): Application
|
|
{
|
|
return Application::factory()->create([
|
|
'environment_id' => $environment->id,
|
|
'destination_id' => $destination->id,
|
|
'destination_type' => $destination->getMorphClass(),
|
|
]);
|
|
}
|
|
|
|
it('loads a resource ignore and a team-wide ignore of the resource team', function () {
|
|
$own = ImageScanIgnore::factory()->create([
|
|
'team_id' => $this->team->id,
|
|
'resource_type' => $this->application->getMorphClass(),
|
|
'resource_id' => $this->application->id,
|
|
'vulnerability_id' => 'CVE-2021-44228',
|
|
]);
|
|
$wide = ImageScanIgnore::factory()->create([
|
|
'team_id' => $this->team->id,
|
|
'vulnerability_id' => null,
|
|
'package' => 'log4j-core',
|
|
]);
|
|
|
|
$matcher = IgnoreMatcher::forResource($this->application);
|
|
|
|
expect($matcher->matches($this->finding)->is($own))->toBeTrue()
|
|
->and($matcher->matches(Finding::fromArray(['id' => 'CVE-2020-1', 'package' => 'log4j-core']))->is($wide))->toBeTrue();
|
|
});
|
|
|
|
it('never loads an ignore of another team', function () {
|
|
$otherTeam = Team::factory()->create();
|
|
ImageScanIgnore::factory()->create(['team_id' => $otherTeam->id, 'vulnerability_id' => 'CVE-2021-44228']);
|
|
ImageScanIgnore::factory()->create([
|
|
'team_id' => $otherTeam->id,
|
|
'resource_type' => $this->application->getMorphClass(),
|
|
'resource_id' => $this->application->id,
|
|
'vulnerability_id' => 'CVE-2021-44228',
|
|
]);
|
|
|
|
expect(IgnoreMatcher::forResource($this->application)->matches($this->finding))->toBeNull();
|
|
});
|
|
|
|
it('does not load an ignore that belongs to another resource', function () {
|
|
$other = ignoreMatcherApplication($this->environment, $this->destination);
|
|
ImageScanIgnore::factory()->create([
|
|
'team_id' => $this->team->id,
|
|
'resource_type' => $other->getMorphClass(),
|
|
'resource_id' => $other->id,
|
|
'vulnerability_id' => 'CVE-2021-44228',
|
|
]);
|
|
|
|
expect(IgnoreMatcher::forResource($this->application)->matches($this->finding))->toBeNull()
|
|
->and(IgnoreMatcher::forResource($other)->matches($this->finding))->not->toBeNull();
|
|
});
|
|
|
|
it('does not load an expired ignore', function () {
|
|
ImageScanIgnore::factory()->create([
|
|
'team_id' => $this->team->id,
|
|
'vulnerability_id' => 'CVE-2021-44228',
|
|
'expires_at' => now()->subMinute(),
|
|
]);
|
|
|
|
expect(IgnoreMatcher::forResource($this->application)->matches($this->finding))->toBeNull();
|
|
});
|
|
|
|
it('matches nothing for a resource without a team', function () {
|
|
ImageScanIgnore::factory()->create(['team_id' => $this->team->id, 'vulnerability_id' => 'CVE-2021-44228']);
|
|
$orphan = new Application;
|
|
|
|
expect(IgnoreMatcher::forResource($orphan)->matches($this->finding))->toBeNull();
|
|
});
|
|
|
|
it('never matches an expired ignore passed in directly', function () {
|
|
$expired = new ImageScanIgnore(['vulnerability_id' => 'CVE-2021-44228', 'reason' => 'x', 'expires_at' => now()->subMinute()]);
|
|
$active = new ImageScanIgnore(['vulnerability_id' => 'CVE-2021-44228', 'reason' => 'x', 'expires_at' => now()->addDay()]);
|
|
|
|
expect(IgnoreMatcher::fromIgnores(collect([$expired]))->matches($this->finding))->toBeNull()
|
|
->and(IgnoreMatcher::fromIgnores(collect([$expired, $active]))->matches($this->finding))->toBe($active);
|
|
});
|
|
|
|
it('stops a never-ending team-wide ignore of a problem that attackers use after 90 days', function (int $days, bool $ignored) {
|
|
$this->travelTo(now()->subDays($days));
|
|
ImageScanIgnore::factory()->create(['team_id' => $this->team->id, 'vulnerability_id' => null, 'package' => 'log4j-core', 'expires_at' => null]);
|
|
$this->travelBack();
|
|
$exploited = Finding::fromArray(['id' => 'CVE-2021-44228', 'package' => 'log4j-core', 'severity' => 'critical', 'kev' => true]);
|
|
|
|
expect(IgnoreMatcher::forResource($this->application)->matches($exploited) !== null)->toBe($ignored);
|
|
})->with([
|
|
'89 days' => [89, true],
|
|
'91 days' => [91, false],
|
|
]);
|
|
|
|
it('keeps a never-ending ignore of a problem without an exploit signal', function () {
|
|
$this->travelTo(now()->subYears(2));
|
|
ImageScanIgnore::factory()->create(['team_id' => $this->team->id, 'vulnerability_id' => 'CVE-2021-44228', 'expires_at' => null]);
|
|
$this->travelBack();
|
|
$plain = Finding::fromArray(['id' => 'CVE-2021-44228', 'package' => 'log4j-core', 'severity' => 'critical', 'kev' => false]);
|
|
|
|
expect(IgnoreMatcher::forResource($this->application)->matches($plain))->not->toBeNull();
|
|
});
|
|
|
|
it('uses another ignore of the finding when a never-ending one stopped', function () {
|
|
$this->travelTo(now()->subDays(100));
|
|
ImageScanIgnore::factory()->create(['team_id' => $this->team->id, 'vulnerability_id' => 'CVE-2021-44228', 'expires_at' => null]);
|
|
$this->travelBack();
|
|
$recent = ImageScanIgnore::factory()->create(['team_id' => $this->team->id, 'vulnerability_id' => null, 'package' => 'log4j-core', 'expires_at' => now()->addDays(30)]);
|
|
$exploited = Finding::fromArray(['id' => 'CVE-2021-44228', 'package' => 'log4j-core', 'severity' => 'critical', 'kev' => true]);
|
|
|
|
expect(IgnoreMatcher::forResource($this->application)->matches($exploited)?->is($recent))->toBeTrue();
|
|
});
|