mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 21:18:11 -04:00
fix(api): use the Cloudflare client IP for allowed IPs and audit logs
Closes #12301 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0f0a1e920d
commit
80ce0efe7e
@@ -35,6 +35,7 @@ use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -170,6 +171,17 @@ test('audit log persists a structured event for the current team', function () {
|
||||
->and($event->metadata['changed'])->toBe(['name']);
|
||||
});
|
||||
|
||||
test('audit log records the Cloudflare client ip when the request comes from Cloudflare', function () {
|
||||
app()->instance('request', Request::create('/', server: [
|
||||
'REMOTE_ADDR' => '162.158.79.140',
|
||||
'HTTP_CF_CONNECTING_IP' => '160.79.104.10',
|
||||
]));
|
||||
|
||||
auditLog('api.application.updated', ['application_uuid' => 'app-123']);
|
||||
|
||||
expect(AuditEvent::query()->sole()->ip_address)->toBe('160.79.104.10');
|
||||
});
|
||||
|
||||
test('auditable models record authenticated create update and delete actions', function () {
|
||||
$project = Project::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
|
||||
@@ -10,7 +10,7 @@ it('uses the Cloudflare client ip on cloud', function () {
|
||||
'HTTP_CF_CONNECTING_IP' => '2001:db8::10',
|
||||
]);
|
||||
|
||||
expect(auth_rate_limit_ip($request))->toBe('2001:db8::10');
|
||||
expect(client_ip($request))->toBe('2001:db8::10');
|
||||
});
|
||||
|
||||
it('falls back to the resolved request ip when the Cloudflare header is invalid', function () {
|
||||
@@ -21,10 +21,21 @@ it('falls back to the resolved request ip when the Cloudflare header is invalid'
|
||||
'HTTP_CF_CONNECTING_IP' => 'invalid',
|
||||
]);
|
||||
|
||||
expect(auth_rate_limit_ip($request))->toBe('203.0.113.10');
|
||||
expect(client_ip($request))->toBe('203.0.113.10');
|
||||
});
|
||||
|
||||
it('ignores the Cloudflare header on self-hosted instances', function () {
|
||||
it('uses the Cloudflare client ip on self-hosted instances when the request comes from Cloudflare', function (string $cloudflareEdge) {
|
||||
config()->set('constants.coolify.self_hosted', true);
|
||||
|
||||
$request = Request::create('/', server: [
|
||||
'REMOTE_ADDR' => $cloudflareEdge,
|
||||
'HTTP_CF_CONNECTING_IP' => '2001:db8::20',
|
||||
]);
|
||||
|
||||
expect(client_ip($request))->toBe('2001:db8::20');
|
||||
})->with(['162.158.79.140', '104.24.0.1', '2a06:98c0::1']);
|
||||
|
||||
it('ignores the Cloudflare header on self-hosted instances when the request does not come from Cloudflare', function () {
|
||||
config()->set('constants.coolify.self_hosted', true);
|
||||
|
||||
$request = Request::create('/', server: [
|
||||
@@ -32,5 +43,5 @@ it('ignores the Cloudflare header on self-hosted instances', function () {
|
||||
'HTTP_CF_CONNECTING_IP' => '2001:db8::20',
|
||||
]);
|
||||
|
||||
expect(auth_rate_limit_ip($request))->toBe('203.0.113.20');
|
||||
expect(client_ip($request))->toBe('203.0.113.20');
|
||||
});
|
||||
|
||||
@@ -143,6 +143,30 @@ test('MCP endpoint accepts a token from an IP inside the allow-list', function (
|
||||
mcpListTools($token)->assertOk();
|
||||
});
|
||||
|
||||
test('MCP endpoint checks the allow-list against the client IP behind Cloudflare', function () {
|
||||
InstanceSettings::query()->where('id', 0)->update(['allowed_ips' => '160.79.104.0/21']);
|
||||
Once::flush();
|
||||
$token = $this->user->createToken('mcp-read', ['read'])->plainTextToken;
|
||||
|
||||
test()->withServerVariables(['REMOTE_ADDR' => '10.0.1.5'])
|
||||
->withHeaders(['X-Forwarded-For' => '172.71.194.182', 'CF-Connecting-IP' => '160.79.104.10']);
|
||||
|
||||
mcpListTools($token)->assertOk();
|
||||
});
|
||||
|
||||
test('MCP endpoint ignores a CF-Connecting-IP header that does not come from Cloudflare', function () {
|
||||
InstanceSettings::query()->where('id', 0)->update(['allowed_ips' => '160.79.104.0/21']);
|
||||
Once::flush();
|
||||
$token = $this->user->createToken('mcp-read', ['read'])->plainTextToken;
|
||||
|
||||
test()->withServerVariables(['REMOTE_ADDR' => '10.0.1.5'])
|
||||
->withHeaders(['X-Forwarded-For' => '198.51.100.7', 'CF-Connecting-IP' => '160.79.104.10']);
|
||||
|
||||
mcpListTools($token)
|
||||
->assertForbidden()
|
||||
->assertJson(['message' => 'You are not allowed to access the API.']);
|
||||
});
|
||||
|
||||
test('MCP endpoint lists tools for an authenticated token', function () {
|
||||
$token = $this->user->createToken('mcp-read', ['read'])->plainTextToken;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user