Merge pull request #6927 from coollabsio/andrasbacsai/fix-terminal-ws
Skip TrustHosts for API and Webhook routes
This commit is contained in:
commit
784dfd50c6
2 changed files with 125 additions and 0 deletions
|
|
@ -4,11 +4,42 @@
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
class TrustHosts extends Middleware
|
class TrustHosts extends Middleware
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Handle the incoming request.
|
||||||
|
*
|
||||||
|
* Skip host validation for certain routes:
|
||||||
|
* - Terminal auth routes (called by realtime container)
|
||||||
|
* - API routes (use token-based authentication, not host validation)
|
||||||
|
* - Webhook endpoints (use cryptographic signature validation)
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, $next)
|
||||||
|
{
|
||||||
|
// Skip host validation for these routes
|
||||||
|
if ($request->is(
|
||||||
|
'terminal/auth',
|
||||||
|
'terminal/auth/ips',
|
||||||
|
'api/*',
|
||||||
|
'webhooks/*'
|
||||||
|
)) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip host validation if no FQDN is configured (initial setup)
|
||||||
|
$fqdnHost = Cache::get('instance_settings_fqdn_host');
|
||||||
|
if ($fqdnHost === '' || $fqdnHost === null) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other routes, use parent's host validation
|
||||||
|
return parent::handle($request, $next);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the host patterns that should be trusted.
|
* Get the host patterns that should be trusted.
|
||||||
*
|
*
|
||||||
|
|
@ -44,6 +75,19 @@ public function hosts(): array
|
||||||
$trustedHosts[] = $fqdnHost;
|
$trustedHosts[] = $fqdnHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trust the APP_URL host itself (not just subdomains)
|
||||||
|
$appUrl = config('app.url');
|
||||||
|
if ($appUrl) {
|
||||||
|
try {
|
||||||
|
$appUrlHost = parse_url($appUrl, PHP_URL_HOST);
|
||||||
|
if ($appUrlHost && ! in_array($appUrlHost, $trustedHosts, true)) {
|
||||||
|
$trustedHosts[] = $appUrlHost;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Ignore parse errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Trust all subdomains of APP_URL as fallback
|
// Trust all subdomains of APP_URL as fallback
|
||||||
$trustedHosts[] = $this->allSubdomainsOfApplicationUrl();
|
$trustedHosts[] = $this->allSubdomainsOfApplicationUrl();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,3 +227,84 @@
|
||||||
// Should only contain APP_URL pattern, not any FQDN
|
// Should only contain APP_URL pattern, not any FQDN
|
||||||
expect($hosts2)->not->toBeEmpty();
|
expect($hosts2)->not->toBeEmpty();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('skips host validation for terminal auth routes', function () {
|
||||||
|
// These routes should be accessible with any Host header (for internal container communication)
|
||||||
|
$response = $this->postJson('/terminal/auth', [], [
|
||||||
|
'Host' => 'coolify:8080', // Internal Docker host
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should not get 400 Bad Host (might get 401 Unauthorized instead)
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips host validation for terminal auth ips route', function () {
|
||||||
|
// These routes should be accessible with any Host header (for internal container communication)
|
||||||
|
$response = $this->postJson('/terminal/auth/ips', [], [
|
||||||
|
'Host' => 'soketi:6002', // Another internal Docker host
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should not get 400 Bad Host (might get 401 Unauthorized instead)
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still enforces host validation for non-terminal routes', function () {
|
||||||
|
InstanceSettings::updateOrCreate(
|
||||||
|
['id' => 0],
|
||||||
|
['fqdn' => 'https://coolify.example.com']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Regular routes should still validate Host header
|
||||||
|
$response = $this->get('/', [
|
||||||
|
'Host' => 'evil.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should get 400 Bad Host for untrusted host
|
||||||
|
expect($response->status())->toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips host validation for API routes', function () {
|
||||||
|
// All API routes use token-based auth (Sanctum), not host validation
|
||||||
|
// They should be accessible from any host (mobile apps, CLI tools, scripts)
|
||||||
|
|
||||||
|
// Test health check endpoint
|
||||||
|
$response = $this->get('/api/health', [
|
||||||
|
'Host' => 'internal-lb.local',
|
||||||
|
]);
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
|
||||||
|
// Test v1 health check
|
||||||
|
$response = $this->get('/api/v1/health', [
|
||||||
|
'Host' => '10.0.0.5',
|
||||||
|
]);
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
|
||||||
|
// Test feedback endpoint
|
||||||
|
$response = $this->post('/api/feedback', [], [
|
||||||
|
'Host' => 'mobile-app.local',
|
||||||
|
]);
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips host validation for webhook endpoints', function () {
|
||||||
|
// All webhook routes are under /webhooks/* prefix (see RouteServiceProvider)
|
||||||
|
// and use cryptographic signature validation instead of host validation
|
||||||
|
|
||||||
|
// Test GitHub webhook
|
||||||
|
$response = $this->post('/webhooks/source/github/events', [], [
|
||||||
|
'Host' => 'github-webhook-proxy.local',
|
||||||
|
]);
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
|
||||||
|
// Test GitLab webhook
|
||||||
|
$response = $this->post('/webhooks/source/gitlab/events/manual', [], [
|
||||||
|
'Host' => 'gitlab.example.com',
|
||||||
|
]);
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
|
||||||
|
// Test Stripe webhook
|
||||||
|
$response = $this->post('/webhooks/payments/stripe/events', [], [
|
||||||
|
'Host' => 'stripe-webhook-forwarder.local',
|
||||||
|
]);
|
||||||
|
expect($response->status())->not->toBe(400);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue