Adds a new Laravel validation rule to prevent path traversal, hidden files, and invalid filenames in the dynamic proxy configuration feature. Validates filenames to ensure they contain only safe characters, don't exceed filesystem limits, and don't use reserved names. - New Rule: ValidProxyConfigFilename with comprehensive validation - Updated: NewDynamicConfiguration to use the new rule - Added: 13 unit tests covering all validation scenarios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
3.6 KiB
PHP
107 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Server\Proxy;
|
|
|
|
use App\Enums\ProxyTypes;
|
|
use App\Models\Server;
|
|
use App\Rules\ValidProxyConfigFilename;
|
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
use Livewire\Component;
|
|
use Symfony\Component\Yaml\Yaml;
|
|
|
|
class NewDynamicConfiguration extends Component
|
|
{
|
|
use AuthorizesRequests;
|
|
|
|
public string $fileName = '';
|
|
|
|
public string $value = '';
|
|
|
|
public bool $newFile = false;
|
|
|
|
public Server $server;
|
|
|
|
public $server_id;
|
|
|
|
public $parameters = [];
|
|
|
|
public function mount()
|
|
{
|
|
$this->server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
|
$this->parameters = get_route_parameters();
|
|
if ($this->fileName !== '') {
|
|
$this->fileName = str_replace('|', '.', $this->fileName);
|
|
}
|
|
}
|
|
|
|
public function addDynamicConfiguration()
|
|
{
|
|
try {
|
|
$this->authorize('update', $this->server);
|
|
$this->validate([
|
|
'fileName' => ['required', new ValidProxyConfigFilename],
|
|
'value' => 'required',
|
|
]);
|
|
|
|
// Additional security validation to prevent command injection
|
|
validateShellSafePath($this->fileName, 'proxy configuration filename');
|
|
|
|
if (data_get($this->parameters, 'server_uuid')) {
|
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
|
|
}
|
|
|
|
if (is_null($this->server)) {
|
|
return redirect()->route('server.index');
|
|
}
|
|
$proxy_type = $this->server->proxyType();
|
|
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
|
if (! str($this->fileName)->endsWith('.yaml') && ! str($this->fileName)->endsWith('.yml')) {
|
|
$this->fileName = "{$this->fileName}.yaml";
|
|
}
|
|
if ($this->fileName === 'coolify.yaml') {
|
|
$this->dispatch('error', 'File name is reserved.');
|
|
|
|
return;
|
|
}
|
|
} elseif ($proxy_type === 'CADDY') {
|
|
if (! str($this->fileName)->endsWith('.caddy')) {
|
|
$this->fileName = "{$this->fileName}.caddy";
|
|
}
|
|
}
|
|
$proxy_path = $this->server->proxyPath();
|
|
$file = "{$proxy_path}/dynamic/{$this->fileName}";
|
|
$escapedFile = escapeshellarg($file);
|
|
|
|
if ($this->newFile) {
|
|
$exists = instant_remote_process(["test -f {$escapedFile} && echo 1 || echo 0"], $this->server);
|
|
if ($exists == 1) {
|
|
$this->dispatch('error', 'File already exists');
|
|
|
|
return;
|
|
}
|
|
}
|
|
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
|
$yaml = Yaml::parse($this->value);
|
|
$yaml = Yaml::dump($yaml, 10, 2);
|
|
$this->value = $yaml;
|
|
}
|
|
$base64_value = base64_encode($this->value);
|
|
instant_remote_process([
|
|
"echo '{$base64_value}' | base64 -d | tee {$escapedFile} > /dev/null",
|
|
], $this->server);
|
|
if ($proxy_type === 'CADDY') {
|
|
$this->server->reloadCaddy();
|
|
}
|
|
$this->dispatch('loadDynamicConfigurations');
|
|
$this->dispatch('dynamic-configuration-added');
|
|
$this->dispatch('success', 'Dynamic configuration saved.');
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.server.proxy.new-dynamic-configuration');
|
|
}
|
|
}
|