mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
refactor(security): remove published APP_KEY request blocking middleware
Drop the RejectPublishedAppKey middleware and its registration in the HTTP kernel, so requests are no longer refused with a 503 when APP_KEY or APP_PREVIOUS_KEYS matches a key shipped in the repository. Rename PublishedAppKeyTest to DecryptWithoutClassesTest and keep only the checks that Sentinel tokens and team channel payloads are decrypted without unserializing objects.
This commit is contained in:
@@ -20,7 +20,6 @@ use App\Http\Middleware\EnsureTokenBelongsToCurrentTeamMember;
|
||||
use App\Http\Middleware\KeepConcurrentTeamSwitch;
|
||||
use App\Http\Middleware\PreventRequestsDuringMaintenance;
|
||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||
use App\Http\Middleware\RejectPublishedAppKey;
|
||||
use App\Http\Middleware\RequirePassword;
|
||||
use App\Http\Middleware\RequireTeamTwoFactor;
|
||||
use App\Http\Middleware\ThrottleFailedApiRequests;
|
||||
@@ -58,7 +57,6 @@ class Kernel extends HttpKernel
|
||||
protected $middleware = [
|
||||
// Outermost so Server-Timing includes the full middleware + app cost.
|
||||
AddServerTimingHeaders::class,
|
||||
RejectPublishedAppKey::class,
|
||||
TrustProxies::class,
|
||||
HandleCors::class,
|
||||
ThrottleFailedApiRequests::class,
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Blocks all HTTP requests while APP_KEY (or APP_PREVIOUS_KEYS) is a key that was published in this repository.
|
||||
*
|
||||
* Anyone can sign and encrypt data with a published key, so the instance is not safe to serve.
|
||||
* Console commands still run, so the key can be rotated with `php artisan app-key:reencrypt`.
|
||||
*/
|
||||
class RejectPublishedAppKey
|
||||
{
|
||||
/**
|
||||
* Keys from example files that were shipped in this repository.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public const PUBLISHED_KEYS = [
|
||||
// .env.windows-docker-desktop.example (removed in v4.6.0)
|
||||
'base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=',
|
||||
];
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (! self::usesPublishedKey()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return response(
|
||||
"Coolify does not start: APP_KEY or APP_PREVIOUS_KEYS contains a key that is public.\n\n".
|
||||
"1. Generate a new key: openssl rand -base64 32\n".
|
||||
"2. In .env, set APP_PREVIOUS_KEYS to the old APP_KEY value and APP_KEY=base64:<new key>.\n".
|
||||
"3. Restart Coolify and run: docker exec coolify php artisan app-key:reencrypt --force\n".
|
||||
"4. Remove APP_PREVIOUS_KEYS from .env and restart Coolify.\n",
|
||||
503,
|
||||
['Content-Type' => 'text/plain; charset=UTF-8'],
|
||||
);
|
||||
}
|
||||
|
||||
public static function usesPublishedKey(): bool
|
||||
{
|
||||
$keys = [config('app.key'), ...(array) config('app.previous_keys', [])];
|
||||
|
||||
return array_intersect(array_map(fn ($key) => trim((string) $key), $keys), self::PUBLISHED_KEYS) !== [];
|
||||
}
|
||||
}
|
||||
+6
-22
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\RejectPublishedAppKey;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Traits\ListensToTeamChannel;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
class PublishedAppKeyTestWakeupProbe
|
||||
class DecryptWithoutClassesTestWakeupProbe
|
||||
{
|
||||
public static bool $woken = false;
|
||||
|
||||
@@ -20,31 +19,16 @@ class PublishedAppKeyTestWakeupProbe
|
||||
beforeEach(function () {
|
||||
config(['app.maintenance.store' => 'array']);
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
PublishedAppKeyTestWakeupProbe::$woken = false;
|
||||
});
|
||||
|
||||
it('refuses HTTP requests when a published key is the current or a previous key', function (array $config) {
|
||||
config($config);
|
||||
|
||||
$this->get('/api/health')
|
||||
->assertStatus(503)
|
||||
->assertSee('app-key:reencrypt');
|
||||
})->with([
|
||||
'current key' => [['app.key' => RejectPublishedAppKey::PUBLISHED_KEYS[0]]],
|
||||
'previous key' => [['app.previous_keys' => [RejectPublishedAppKey::PUBLISHED_KEYS[0]]]],
|
||||
]);
|
||||
|
||||
it('serves HTTP requests with a private key', function () {
|
||||
$this->get('/api/health')->assertOk();
|
||||
DecryptWithoutClassesTestWakeupProbe::$woken = false;
|
||||
});
|
||||
|
||||
it('does not unserialize objects from a Sentinel token', function () {
|
||||
$token = encrypt(new PublishedAppKeyTestWakeupProbe);
|
||||
$token = encrypt(new DecryptWithoutClassesTestWakeupProbe);
|
||||
|
||||
$this->postJson('/api/v1/sentinel/push', [], ['Authorization' => 'Bearer '.$token])
|
||||
->assertUnauthorized();
|
||||
|
||||
expect(PublishedAppKeyTestWakeupProbe::$woken)->toBeFalse();
|
||||
expect(DecryptWithoutClassesTestWakeupProbe::$woken)->toBeFalse();
|
||||
});
|
||||
|
||||
it('does not unserialize objects from a team channel payload', function () {
|
||||
@@ -58,7 +42,7 @@ it('does not unserialize objects from a team channel payload', function () {
|
||||
}
|
||||
};
|
||||
|
||||
expect($component->read(['payload' => encrypt(['teamId' => 1, 'probe' => new PublishedAppKeyTestWakeupProbe])]))
|
||||
expect($component->read(['payload' => encrypt(['teamId' => 1, 'probe' => new DecryptWithoutClassesTestWakeupProbe])]))
|
||||
->toHaveKey('teamId', 1)
|
||||
->and(PublishedAppKeyTestWakeupProbe::$woken)->toBeFalse();
|
||||
->and(DecryptWithoutClassesTestWakeupProbe::$woken)->toBeFalse();
|
||||
});
|
||||
Reference in New Issue
Block a user