diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 4b0af48c2a..1a5d24b194 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -20,6 +20,7 @@ use App\Rules\ValidServerIp; use App\Support\ValidationPatterns; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Arr; use Illuminate\Validation\ValidationException; use OpenApi\Attributes as OA; use Stringable; @@ -47,14 +48,20 @@ class ServersController extends Controller $server->makeHidden([ 'id', ]); - if (request()->attributes->get('can_read_sensitive', false) === true) { + $canReadSensitive = request()->attributes->get('can_read_sensitive', false) === true; + if ($canReadSensitive) { $server->makeVisible([ 'logdrain_axiom_api_key', 'logdrain_newrelic_license_key', ]); } - return serializeApiResponse($server); + $serialized = serializeApiResponse($server); + if (! $canReadSensitive && is_array($serialized->get('proxy'))) { + $serialized->put('proxy', Arr::except($serialized->get('proxy'), ['last_saved_proxy_configuration'])); + } + + return $serialized; } #[OA\Get( diff --git a/tests/Feature/Security/ApiSensitiveFieldsTest.php b/tests/Feature/Security/ApiSensitiveFieldsTest.php index 5420ba8c18..aac60c3cb3 100644 --- a/tests/Feature/Security/ApiSensitiveFieldsTest.php +++ b/tests/Feature/Security/ApiSensitiveFieldsTest.php @@ -135,6 +135,32 @@ describe('GET /api/v1/servers sensitive field gating', function () { expect($body)->toContain('logdrain_axiom_api_key'); }); + test('saved proxy configuration in server detail requires read sensitive', function () { + $this->server->proxy->set('last_saved_proxy_configuration', "services:\n traefik:\n environment:\n TOKEN: stored-proxy-config-value\n"); + $this->server->save(); + + $readToken = makeApiToken($this->user, $this->team, ['read']); + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$readToken, + ])->getJson("/api/v1/servers/{$this->server->uuid}"); + + $response->assertStatus(200); + expect($response->getContent())->not->toContain('stored-proxy-config-value') + ->and($response->json('proxy'))->toBeArray(); + }); + + test('read sensitive token sees saved proxy configuration in server detail', function () { + $this->server->proxy->set('last_saved_proxy_configuration', "services:\n traefik:\n environment:\n TOKEN: stored-proxy-config-value\n"); + $this->server->save(); + + $sensitiveToken = makeApiToken($this->user, $this->team, ['read', 'read:sensitive']); + $this->withHeaders([ + 'Authorization' => 'Bearer '.$sensitiveToken, + ])->getJson("/api/v1/servers/{$this->server->uuid}") + ->assertStatus(200) + ->assertJsonPath('proxy.last_saved_proxy_configuration', "services:\n traefik:\n environment:\n TOKEN: stored-proxy-config-value\n"); + }); + test('server resources response does not leak server secrets', function () { $token = makeApiToken($this->user, $this->team, ['read', 'read:sensitive']);