Files
portal/app/app/Http/Middleware/EnsureSaasAdmin.php
T

65 lines
3.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Гейт SaaS-admin зоны (/api/admin/*) — audit-находка J2.
*
* СТОПГЭП (2026-05-25): защита боевой админ-зоны (/admin + /api/admin/*)
* перенесена на уровень nginx — отдельный HTTP Basic Auth с собственным
* паролем (`/etc/nginx/.htpasswd-admin`, location ^~ /admin и ^~ /api/admin).
* Поэтому middleware больше не закрывает зону на проде: дверь держит nginx.
*
* Ранее (Sprint 3F) здесь был fail-closed 503 вне dev/testing — он закрывал
* всю админку на проде наглухо, т.к. настоящий saas-admin SSO (Yandex 360)
* ещё не готов (гейтится Б-1 + DO-4). Замок 503 снят осознанно: оголять
* /api/admin/* в интернет нельзя, но nginx-пароль её прикрывает.
*
* admin_user_id для audit-trail по-прежнему резолвится трейтом
* ResolvesAdminUserId (стаб super_admin) — это отдельная зона.
*
* G7-B: пока активен impersonation (маркер сессии ИЛИ машинный ключ) —
* вход в saas-admin зону запрещён (запрет эскалации к другим тенантам).
*
* M-1 (приёмка 21.06): nginx-дверь дополнена app-слойным fail-closed гейтом по
* REMOTE_USER + config-allowlist. Закрывает обходы front-controller'а
* (/index.php/api/admin, /API/admin), где nginx basic-auth не применяется и
* REMOTE_USER пуст. См. config/admin.php и spec 2026-06-21-m1-admin-gate-fail-closed.
*
* TODO (после Б-1 + DO-4): заменить nginx-дверь на настоящий saas-admin
* guard (Yandex 360 SSO-сессия + роль).
*/
class EnsureSaasAdmin
{
public function handle(Request $request, Closure $next): Response
{
// G7-B: пока активен impersonation (маркер сессии ИЛИ машинный ключ) —
// вход в saas-admin зону запрещён (запрет эскалации к другим тенантам).
$hasMarker = $request->hasSession() && $request->session()->has('impersonation');
$hasBearer = str_starts_with((string) $request->header('Authorization', ''), 'Bearer lpimp_');
if ($hasMarker || $hasBearer) {
abort(403, 'Во время сессии impersonation доступ в админ-зону запрещён.');
}
// M-1 (приёмка 21.06): fail-closed гейт. REMOTE_USER непуст только у запросов,
// прошедших nginx admin-basic-auth (^~ /admin, ^~ /api/admin); обходы через
// front-controller (/index.php/api/admin, /API/admin) попадают в auth_basic off
// → REMOTE_USER пуст → 403. В local/testing гейт выключен (см. config/admin.php).
if (config('admin.basic_auth_gate')) {
$remoteUser = (string) $request->server('REMOTE_USER', '');
$allowlist = (array) config('admin.basic_auth_allowlist', []);
if ($remoteUser === '' || ! in_array($remoteUser, $allowlist, true)) {
abort(403, 'Доступ в админ-зону запрещён.');
}
}
return $next($request);
}
}