mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(log-drains): validate custom log drain configuration (#12197)
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Actions\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Rules\ValidFluentBitConfig;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartLogDrain
|
||||
@@ -131,6 +132,14 @@ class StartLogDrain
|
||||
if (! $server->settings->is_logdrain_custom_enabled) {
|
||||
throw new \Exception('Custom log drain is not enabled.');
|
||||
}
|
||||
$configError = (new ValidFluentBitConfig)->check($server->settings->logdrain_custom_config);
|
||||
if ($configError !== null) {
|
||||
throw new \Exception("Custom log drain config is not allowed: {$configError}");
|
||||
}
|
||||
$parsersError = (new ValidFluentBitConfig(parsersOnly: true))->check($server->settings->logdrain_custom_config_parser);
|
||||
if ($parsersError !== null) {
|
||||
throw new \Exception("Custom log drain parser config is not allowed: {$parsersError}");
|
||||
}
|
||||
$config = base64_encode($server->settings->logdrain_custom_config);
|
||||
$parsers = base64_encode($server->settings->logdrain_custom_config_parser);
|
||||
} else {
|
||||
@@ -153,8 +162,8 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./fluent-bit.conf:/fluent-bit.conf
|
||||
- ./parsers.conf:/parsers.conf
|
||||
- ./fluent-bit.conf:/fluent-bit.conf:ro
|
||||
- ./parsers.conf:/parsers.conf:ro
|
||||
ports:
|
||||
- 127.0.0.1:24224:24224
|
||||
labels:
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Actions\Server\StartLogDrain;
|
||||
use App\Actions\Server\StopLogDrain;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server;
|
||||
use App\Rules\ValidFluentBitConfig;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
@@ -108,7 +109,7 @@ class ServerLogDrainsController extends Controller
|
||||
|
||||
#[OA\Patch(
|
||||
summary: 'Update log drain settings',
|
||||
description: 'Update New Relic, Axiom, or custom log drain settings for a server owned by the authenticated team. Changing `logdrain_custom_config` or `logdrain_custom_config_parser` needs a token with the `deploy` permission.',
|
||||
description: 'Update New Relic, Axiom, or custom log drain settings for a server owned by the authenticated team. Changing `logdrain_custom_config` or `logdrain_custom_config_parser` needs a token with the `deploy` permission. Custom configs cannot use the lua, wasm, exec, exec_wasi, or file plugins, @INCLUDE, or plugins_file, and the parser file can only contain [PARSER] and [MULTILINE_PARSER] sections.',
|
||||
path: '/servers/{uuid}/log-drains',
|
||||
operationId: 'update-server-log-drains',
|
||||
security: [['bearerAuth' => []]],
|
||||
@@ -137,6 +138,7 @@ class ServerLogDrainsController extends Controller
|
||||
new OA\Response(response: 200, description: 'Updated log drain settings.'),
|
||||
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||
new OA\Response(response: 403, description: 'Missing the deploy permission for a custom log drain change.'),
|
||||
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||
new OA\Response(response: 422, ref: '#/components/responses/422'),
|
||||
],
|
||||
@@ -160,6 +162,14 @@ class ServerLogDrainsController extends Controller
|
||||
|
||||
$this->authorize('update', $server);
|
||||
|
||||
// The custom config is applied on the server, like a deployment.
|
||||
$customConfigChanged = collect(['logdrain_custom_config', 'logdrain_custom_config_parser'])
|
||||
->contains(fn (string $field) => $request->has($field)
|
||||
&& (! is_string($request->input($field) ?? '') || (string) $request->input($field) !== (string) $server->settings->{$field}));
|
||||
if ($customConfigChanged && ! $request->user()->tokenCan('deploy') && ! $request->user()->tokenCan('root')) {
|
||||
return response()->json(['message' => 'Missing required permissions: deploy'], 403);
|
||||
}
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'is_logdrain_newrelic_enabled' => 'boolean',
|
||||
'logdrain_newrelic_license_key' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9_\-\.]+$/'],
|
||||
@@ -168,8 +178,8 @@ class ServerLogDrainsController extends Controller
|
||||
'logdrain_axiom_dataset_name' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9_\-\.]+$/'],
|
||||
'logdrain_axiom_api_key' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9_\-\.]+$/'],
|
||||
'is_logdrain_custom_enabled' => 'boolean',
|
||||
'logdrain_custom_config' => 'nullable|string',
|
||||
'logdrain_custom_config_parser' => 'nullable|string',
|
||||
'logdrain_custom_config' => ['nullable', 'string', new ValidFluentBitConfig],
|
||||
'logdrain_custom_config_parser' => ['nullable', 'string', new ValidFluentBitConfig(parsersOnly: true)],
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), self::ALLOWED_FIELDS);
|
||||
@@ -187,13 +197,6 @@ class ServerLogDrainsController extends Controller
|
||||
|
||||
$settings = $server->settings;
|
||||
|
||||
// The custom config is applied on the server, like a deployment.
|
||||
$customConfigChanged = collect(['logdrain_custom_config', 'logdrain_custom_config_parser'])
|
||||
->contains(fn (string $field) => $request->has($field) && (string) $request->input($field) !== (string) $settings->{$field});
|
||||
if ($customConfigChanged && ! $request->user()->tokenCan('deploy') && ! $request->user()->tokenCan('root')) {
|
||||
return response()->json(['message' => 'Missing required permissions: deploy'], 403);
|
||||
}
|
||||
|
||||
foreach (self::ALLOWED_FIELDS as $field) {
|
||||
if ($request->has($field)) {
|
||||
$settings->{$field} = $request->input($field);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Livewire\Server;
|
||||
use App\Actions\Server\StartLogDrain;
|
||||
use App\Actions\Server\StopLogDrain;
|
||||
use App\Models\Server;
|
||||
use App\Rules\ValidFluentBitConfig;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
@@ -158,8 +159,8 @@ class LogDrains extends Component
|
||||
} elseif ($this->isLogDrainCustomEnabled) {
|
||||
try {
|
||||
$this->validate([
|
||||
'logDrainCustomConfig' => ['required'],
|
||||
'logDrainCustomConfigParser' => ['string', 'nullable'],
|
||||
'logDrainCustomConfig' => ['required', new ValidFluentBitConfig],
|
||||
'logDrainCustomConfigParser' => ['string', 'nullable', new ValidFluentBitConfig(parsersOnly: true)],
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isLogDrainCustomEnabled = false;
|
||||
@@ -279,8 +280,8 @@ class LogDrains extends Component
|
||||
'logDrainAxiomApiKey' => ['required', 'regex:/^[a-zA-Z0-9_\-\.]+$/'],
|
||||
]),
|
||||
'custom' => $this->validate([
|
||||
'logDrainCustomConfig' => ['required'],
|
||||
'logDrainCustomConfigParser' => ['string', 'nullable'],
|
||||
'logDrainCustomConfig' => ['required', new ValidFluentBitConfig],
|
||||
'logDrainCustomConfigParser' => ['string', 'nullable', new ValidFluentBitConfig(parsersOnly: true)],
|
||||
]),
|
||||
default => throw new \InvalidArgumentException('Unknown log drain type.'),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
/**
|
||||
* Validates a custom Fluent Bit classic-mode file for the log drain container.
|
||||
*
|
||||
* Only a supported subset of sections, directives, and plugins is allowed. With $parsersOnly, the value
|
||||
* must only contain [PARSER] and [MULTILINE_PARSER] declarations.
|
||||
*/
|
||||
class ValidFluentBitConfig implements ValidationRule
|
||||
{
|
||||
private const CONFIG_SECTIONS = ['SERVICE', 'INPUT', 'FILTER', 'OUTPUT', 'PARSER', 'MULTILINE_PARSER'];
|
||||
|
||||
private const PARSER_SECTIONS = ['PARSER', 'MULTILINE_PARSER'];
|
||||
|
||||
private const PLUGIN_SECTIONS = ['INPUT', 'FILTER', 'OUTPUT'];
|
||||
|
||||
private const DENIED_PLUGINS = ['lua', 'wasm', 'exec', 'exec_wasi', 'file'];
|
||||
|
||||
private const DENIED_KEYS = ['plugins_file'];
|
||||
|
||||
public function __construct(private bool $parsersOnly = false) {}
|
||||
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
$error = $this->check($value);
|
||||
if ($error !== null) {
|
||||
$fail("The :attribute is not allowed: {$error}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason the value is not allowed, or null when it is safe.
|
||||
*/
|
||||
public function check(mixed $value): ?string
|
||||
{
|
||||
if (blank($value)) {
|
||||
return null;
|
||||
}
|
||||
if (! is_string($value)) {
|
||||
return 'it must be a string.';
|
||||
}
|
||||
|
||||
$allowedSections = $this->parsersOnly ? self::PARSER_SECTIONS : self::CONFIG_SECTIONS;
|
||||
$section = null;
|
||||
|
||||
foreach (preg_split('/\r\n|\r|\n/', $value) as $number => $rawLine) {
|
||||
$line = trim($rawLine);
|
||||
$lineNumber = $number + 1;
|
||||
if ($line === '' || str_starts_with($line, '#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_starts_with($line, '@')) {
|
||||
$directive = strtolower(preg_split('/\s+/', $line)[0]);
|
||||
if ($this->parsersOnly || $directive !== '@set') {
|
||||
return "line {$lineNumber} uses the {$directive} directive.";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_starts_with($line, '[')) {
|
||||
if (! preg_match('/^\[\s*([A-Za-z_]+)\s*\]$/', $line, $matches)) {
|
||||
return "line {$lineNumber} is not a valid section header.";
|
||||
}
|
||||
$section = strtoupper($matches[1]);
|
||||
if (! in_array($section, $allowedSections, true)) {
|
||||
return "line {$lineNumber} uses the [{$section}] section. Allowed sections: ".implode(', ', $allowedSections).'.';
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($section === null) {
|
||||
return "line {$lineNumber} is outside a section.";
|
||||
}
|
||||
|
||||
$parts = preg_split('/\s+/', $line, 2);
|
||||
$key = strtolower($parts[0]);
|
||||
if (! preg_match('/^[a-z_][a-z0-9_.\-]*$/', $key)) {
|
||||
return "line {$lineNumber} has an invalid key.";
|
||||
}
|
||||
if (in_array($key, self::DENIED_KEYS, true)) {
|
||||
return "line {$lineNumber} uses the {$key} key.";
|
||||
}
|
||||
|
||||
if ($key === 'name' && in_array($section, self::PLUGIN_SECTIONS, true)) {
|
||||
$plugin = strtolower(explode('://', trim($parts[1] ?? ''), 2)[0]);
|
||||
if (! preg_match('/^[a-z0-9_]+$/', $plugin)) {
|
||||
return "line {$lineNumber} must name a plugin literally.";
|
||||
}
|
||||
if (in_array($plugin, self::DENIED_PLUGINS, true)) {
|
||||
return "line {$lineNumber} uses the {$plugin} plugin.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -16575,7 +16575,7 @@
|
||||
"Servers"
|
||||
],
|
||||
"summary": "Update log drain settings",
|
||||
"description": "Update New Relic, Axiom, or custom log drain settings for a server owned by the authenticated team. Changing `logdrain_custom_config` or `logdrain_custom_config_parser` needs a token with the `deploy` permission.",
|
||||
"description": "Update New Relic, Axiom, or custom log drain settings for a server owned by the authenticated team. Changing `logdrain_custom_config` or `logdrain_custom_config_parser` needs a token with the `deploy` permission. Custom configs cannot use the lua, wasm, exec, exec_wasi, or file plugins, @INCLUDE, or plugins_file, and the parser file can only contain [PARSER] and [MULTILINE_PARSER] sections.",
|
||||
"operationId": "update-server-log-drains",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -16637,6 +16637,9 @@
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"403": {
|
||||
"description": "Missing the deploy permission for a custom log drain change."
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#\/components\/responses\/404"
|
||||
},
|
||||
@@ -24012,6 +24015,9 @@
|
||||
"is_env_sorting_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_coolify_default_variables": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_container_label_readonly_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
+5
-1
@@ -10522,7 +10522,7 @@ paths:
|
||||
tags:
|
||||
- Servers
|
||||
summary: 'Update log drain settings'
|
||||
description: 'Update New Relic, Axiom, or custom log drain settings for a server owned by the authenticated team. Changing `logdrain_custom_config` or `logdrain_custom_config_parser` needs a token with the `deploy` permission.'
|
||||
description: 'Update New Relic, Axiom, or custom log drain settings for a server owned by the authenticated team. Changing `logdrain_custom_config` or `logdrain_custom_config_parser` needs a token with the `deploy` permission. Custom configs cannot use the lua, wasm, exec, exec_wasi, or file plugins, @INCLUDE, or plugins_file, and the parser file can only contain [PARSER] and [MULTILINE_PARSER] sections.'
|
||||
operationId: update-server-log-drains
|
||||
parameters:
|
||||
-
|
||||
@@ -10564,6 +10564,8 @@ paths:
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'403':
|
||||
description: 'Missing the deploy permission for a custom log drain change.'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
'422':
|
||||
@@ -15309,6 +15311,8 @@ components:
|
||||
type: boolean
|
||||
is_env_sorting_enabled:
|
||||
type: boolean
|
||||
include_coolify_default_variables:
|
||||
type: boolean
|
||||
is_container_label_readonly_enabled:
|
||||
type: boolean
|
||||
is_preserve_repository_enabled:
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
use App\Actions\Server\StartLogDrain;
|
||||
use App\Actions\Server\StopLogDrain;
|
||||
use App\Livewire\Server\LogDrains;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Rules\ValidFluentBitConfig;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
const SAFE_CUSTOM_LOG_DRAIN_CONFIG = <<<'CONF'
|
||||
[SERVICE]
|
||||
Flush 5
|
||||
Parsers_File parsers.conf
|
||||
@SET region=eu
|
||||
[INPUT]
|
||||
Name forward
|
||||
[FILTER]
|
||||
Name grep
|
||||
Match *
|
||||
Exclude log 127.0.0.1
|
||||
[OUTPUT]
|
||||
Name http
|
||||
Match *
|
||||
Host logs.example.com
|
||||
Port 443
|
||||
CONF;
|
||||
|
||||
const SAFE_CUSTOM_LOG_DRAIN_PARSERS = <<<'CONF'
|
||||
# Skip empty lines
|
||||
[PARSER]
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
[MULTILINE_PARSER]
|
||||
name multiline-regex
|
||||
type regex
|
||||
rule "start_state" "/^\d+/" "cont"
|
||||
CONF;
|
||||
|
||||
function customLogDrainConfigWith(string $section): string
|
||||
{
|
||||
return "[INPUT]\n Name forward\n{$section}\n[OUTPUT]\n Name stdout\n Match *\n";
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
Server::flushIdentityMap();
|
||||
config([
|
||||
'app.maintenance.store' => 'array',
|
||||
'cache.default' => 'array',
|
||||
'cache.stores.redis.driver' => 'array',
|
||||
]);
|
||||
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
});
|
||||
|
||||
function customLogDrainPatch(string $token, string $serverUuid, array $payload)
|
||||
{
|
||||
return test()->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
'Accept' => 'application/json',
|
||||
])->patchJson("/api/v1/servers/{$serverUuid}/log-drains", $payload);
|
||||
}
|
||||
|
||||
describe('ValidFluentBitConfig rule', function () {
|
||||
test('accepts normal configs and parser files', function () {
|
||||
expect((new ValidFluentBitConfig)->check(SAFE_CUSTOM_LOG_DRAIN_CONFIG))->toBeNull()
|
||||
->and((new ValidFluentBitConfig(parsersOnly: true))->check(SAFE_CUSTOM_LOG_DRAIN_PARSERS))->toBeNull()
|
||||
->and((new ValidFluentBitConfig)->check(null))->toBeNull();
|
||||
});
|
||||
|
||||
test('rejects unsupported plugins and directives', function (string $config) {
|
||||
expect((new ValidFluentBitConfig)->check($config))->not->toBeNull();
|
||||
})->with([
|
||||
'lua filter' => customLogDrainConfigWith("[FILTER]\n Name lua\n Match *\n script /parsers.conf\n call cb"),
|
||||
'lua filter, other case' => customLogDrainConfigWith("[filter]\n\tNAME\tLua\n Match *"),
|
||||
'wasm filter' => customLogDrainConfigWith("[FILTER]\n Name wasm\n Match *"),
|
||||
'exec input' => "[INPUT]\n Name exec\n",
|
||||
'exec_wasi input' => "[INPUT]\n Name exec_wasi\n",
|
||||
'file output' => "[INPUT]\n Name forward\n[OUTPUT]\n Name file\n Path /etc\n",
|
||||
'plugin name with protocol' => "[INPUT]\n Name exec://x\n",
|
||||
'plugin name from a variable' => "@SET p=lua\n".customLogDrainConfigWith("[FILTER]\n Name \${p}\n Match *"),
|
||||
'include directive' => "@INCLUDE /parsers.conf\n",
|
||||
'plugins section' => "[PLUGINS]\n Path /parsers.conf\n",
|
||||
'plugins_file key' => "[SERVICE]\n Plugins_File /parsers.conf\n",
|
||||
'malformed section header' => "[FILTER] x\n Name lua\n",
|
||||
'key outside a section' => "Name lua\n",
|
||||
]);
|
||||
|
||||
test('rejects parser files that are not parser declarations', function (string $parsers) {
|
||||
expect((new ValidFluentBitConfig(parsersOnly: true))->check($parsers))->not->toBeNull();
|
||||
})->with([
|
||||
'script code' => "function cb(tag, timestamp, record)\n return 1, timestamp, record\nend\n",
|
||||
'input section' => "[INPUT]\n Name forward\n",
|
||||
'set directive' => "@SET a=b\n[PARSER]\n Name x\n",
|
||||
]);
|
||||
});
|
||||
|
||||
describe('custom log drain API', function () {
|
||||
test('a write-only token cannot change a custom log drain', function (array $payload) {
|
||||
Queue::fake();
|
||||
$token = $this->user->createToken('write-only', ['write'])->plainTextToken;
|
||||
|
||||
customLogDrainPatch($token, $this->server->uuid, $payload)
|
||||
->assertForbidden()
|
||||
->assertJsonPath('message', 'Missing required permissions: deploy');
|
||||
|
||||
$settings = $this->server->settings->fresh();
|
||||
expect((bool) $settings->is_logdrain_custom_enabled)->toBeFalse()
|
||||
->and($settings->logdrain_custom_config)->toBeNull();
|
||||
Queue::assertNothingPushed();
|
||||
})->with([
|
||||
'enable with config' => [['is_logdrain_custom_enabled' => true, 'logdrain_custom_config' => SAFE_CUSTOM_LOG_DRAIN_CONFIG]],
|
||||
'config only' => [['logdrain_custom_config' => SAFE_CUSTOM_LOG_DRAIN_CONFIG]],
|
||||
'parser only' => [['logdrain_custom_config_parser' => SAFE_CUSTOM_LOG_DRAIN_PARSERS]],
|
||||
]);
|
||||
|
||||
test('a write-only token can still change the built-in log drains', function () {
|
||||
Queue::fake();
|
||||
$token = $this->user->createToken('write-only', ['write'])->plainTextToken;
|
||||
|
||||
customLogDrainPatch($token, $this->server->uuid, [
|
||||
'logdrain_axiom_dataset_name' => 'api-dataset',
|
||||
'logdrain_axiom_api_key' => 'axiom-key-123',
|
||||
])->assertOk();
|
||||
|
||||
expect($this->server->settings->fresh()->logdrain_axiom_dataset_name)->toBe('api-dataset');
|
||||
});
|
||||
|
||||
test('a deploy token can save a safe custom log drain', function () {
|
||||
Queue::fake();
|
||||
$token = $this->user->createToken('write-deploy', ['write', 'deploy'])->plainTextToken;
|
||||
|
||||
customLogDrainPatch($token, $this->server->uuid, [
|
||||
'is_logdrain_custom_enabled' => true,
|
||||
'logdrain_custom_config' => SAFE_CUSTOM_LOG_DRAIN_CONFIG,
|
||||
'logdrain_custom_config_parser' => SAFE_CUSTOM_LOG_DRAIN_PARSERS,
|
||||
])->assertOk();
|
||||
|
||||
$settings = $this->server->settings->fresh();
|
||||
expect((bool) $settings->is_logdrain_custom_enabled)->toBeTrue()
|
||||
->and($settings->logdrain_custom_config)->toBe(SAFE_CUSTOM_LOG_DRAIN_CONFIG);
|
||||
});
|
||||
|
||||
test('a deploy token cannot save an unsupported custom log drain', function (array $payload, string $errorField) {
|
||||
Queue::fake();
|
||||
$token = $this->user->createToken('write-deploy', ['write', 'deploy'])->plainTextToken;
|
||||
|
||||
customLogDrainPatch($token, $this->server->uuid, ['is_logdrain_custom_enabled' => true, ...$payload])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors($errorField);
|
||||
|
||||
expect((bool) $this->server->settings->fresh()->is_logdrain_custom_enabled)->toBeFalse();
|
||||
Queue::assertNothingPushed();
|
||||
})->with([
|
||||
'lua filter' => [
|
||||
['logdrain_custom_config' => customLogDrainConfigWith("[FILTER]\n Name lua\n Match *\n script /parsers.conf\n call cb")],
|
||||
'logdrain_custom_config',
|
||||
],
|
||||
'parser file with script code' => [
|
||||
['logdrain_custom_config' => SAFE_CUSTOM_LOG_DRAIN_CONFIG, 'logdrain_custom_config_parser' => "function cb()\nend\n"],
|
||||
'logdrain_custom_config_parser',
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('StartLogDrain does not start a stored unsupported custom log drain', function () {
|
||||
StopLogDrain::mock()->shouldReceive('handle')->andReturn('stopped');
|
||||
$this->server->settings->forceFill([
|
||||
'is_logdrain_custom_enabled' => true,
|
||||
'logdrain_custom_config' => customLogDrainConfigWith("[FILTER]\n Name lua\n Match *\n script /parsers.conf\n call cb"),
|
||||
'logdrain_custom_config_parser' => SAFE_CUSTOM_LOG_DRAIN_PARSERS,
|
||||
])->save();
|
||||
|
||||
expect(fn () => StartLogDrain::run($this->server->fresh()))
|
||||
->toThrow(Exception::class, 'Custom log drain config is not allowed: line 4 uses the lua plugin.');
|
||||
});
|
||||
|
||||
test('the UI does not enable an unsupported custom log drain', function () {
|
||||
$this->actingAs($this->user);
|
||||
StartLogDrain::mock()->shouldNotReceive('handle');
|
||||
|
||||
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
|
||||
->set('logDrainCustomConfig', customLogDrainConfigWith("[FILTER]\n Name wasm\n Match *"))
|
||||
->call('toggleLogDrain', 'custom')
|
||||
->assertDispatched('error')
|
||||
->assertSet('isLogDrainCustomEnabled', false);
|
||||
|
||||
expect((bool) $this->server->settings->fresh()->is_logdrain_custom_enabled)->toBeFalse();
|
||||
});
|
||||
Reference in New Issue
Block a user