Сервис проверяет право через отдельный PDP, но при его недоступности разрешает операцию. Как изменить повед...

Сервис проверяет право через отдельный PDP, но при его недоступности разрешает операцию. Как изменить поведение, чтобы сохранить безопасную границу доступа?

async function canTransfer(user, amount) {
  try {
    return await policy.decide(user, "transfer", amount);
  } catch (error) {
    return true;
  }
}
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

При недоступности PDP решение должно быть запрещающим по умолчанию: ошибка проверки не должна превращаться в разрешение. В простейшем варианте обработчик должен вернуть false или завершить операцию отказом; допустимые исключения требуют явно спроектированного, ограниченного механизма аварийного доступа.

Исторический контекст

В распределённых системах авторизацию часто выносят в отдельный Policy Decision Point, чтобы централизовать правила и не дублировать их в сервисах. Это уменьшает расхождения политик, но добавляет сетевую зависимость: сервис больше не может считать доступ разрешённым только потому, что запрос технически корректен.

Подход fail closed сформировался как ответ на опасную ошибку отказоустойчивости: система, пытаясь продолжить работу при сбое защитного компонента, случайно открывает доступ. Для механизмов безопасности недоступность доказательства права обычно безопаснее, чем его отсутствие трактовать как разрешение.

Постановка проблемы

В исходном коде исключение из policy.decide приводит к true. Если PDP недоступен из-за сетевого сбоя, перегрузки или атаки на доступность, пользователь может выполнить перевод без подтверждённого права.

Последствие зависит от операции: для перевода денег, изменения ролей или доступа к персональным данным это может стать прямым обходом авторизации. Одновременно безусловный отказ при любом сбое способен остановить критичный бизнес-процесс, поэтому необходимо учитывать доступность, кэширование и аварийные процедуры.

Подробное решение

Минимальное исправление — отказ при ошибке проверки:

async function canTransfer(user, amount) { try { return await policy.decide(user, "transfer", amount); } catch (error) { return false; } }

При таком поведении сбой PDP сохраняет границу доступа: операция выполняется только после положительного решения. Ошибку следует отличать от обычного ответа «запрещено», чтобы отдельно мониторить доступность PDP, но наружу не раскрывать лишние внутренние детали.

Для снижения влияния отказов используют кэш разрешений, однако кэш должен иметь ограниченный срок жизни, быть привязан к субъекту, ресурсу и действию, а его политика истечения должна учитывать отзыв ролей. Для чувствительных операций безопаснее не принимать решение по устаревшему кэшу вообще; для менее критичных операций можно применять короткий TTL, если риск явно принят.

Иногда нужен аварийный доступ. Его нельзя реализовывать общим catch, разрешающим все запросы. Нужны узкая область действий, отдельная учётная запись или роль, многофакторная аутентификация, ограничение по времени, журналирование и последующий контроль; при этом аварийный режим должен быть явно виден мониторингу.

Также следует ограничить повторные попытки и таймауты, чтобы PDP не стал причиной каскадных отказов. Защита от отказа в доступности не должна менять результат авторизации на разрешение: circuit breaker может быстро возвращать ошибку, но не положительное решение.

Ситуация из практики

Платёжный сервис обращался к PDP перед переводом средств. Рассматривались три варианта: разрешать операции при таймауте, использовать последний результат без ограничения срока или запрещать операцию при отсутствии свежего решения.

Первый вариант обеспечивал доступность, но превращал сетевую атаку или сбой PDP в обход контроля. Второй уменьшал число отказов, однако устаревшее решение могло сохранить доступ после увольнения сотрудника или отзыва полномочий. Третий вариант снижал доступность переводов во время сбоя, зато сохранял конфиденциальность и целостность средств.

Выбрали третий вариант для переводов и короткоживущий кэш только для операций чтения с низким риском. Для экстренных платежей предусмотрели отдельную процедуру с повышенной аутентификацией и обязательным аудитом. В результате отказ PDP блокировал только новые чувствительные операции, а не создавал несанкционированный доступ.

Что кандидаты часто упускают

  1. Всегда ли отказ по умолчанию означает отсутствие кэша?

Нет. Кэш допустим, если заранее определены срок годности, область применимости и последствия устаревания. Важно, что при отсутствии свежего и допустимого решения система не должна самовольно переходить к разрешению; использование кэша — это отдельная политика, а не исключение из fail closed.

  1. Чем опасно просто вернуть false при любой ошибке PDP?

Такой код сохраняет безопасность доступа, но может скрыть серьёзную деградацию и вызвать массовый отказ в обслуживании. Нужно разделять результат «политика запретила» и техническую ошибку, вести метрики, алерты и трассировку, не раскрывая клиенту внутреннюю причину. Отказоустойчивость достигается таймаутами, резервированием и контролируемым кэшированием, а не разрешением при сбое.

  1. Можно ли считать локально проверенную роль заменой недоступному PDP?

Только если это предусмотрено моделью авторизации. Локальная роль может быть устаревшей, не учитывать контекст ресурса, делегирование, отзыв полномочий или ограничения по времени. Такой fallback допустим лишь при чётко ограниченных действиях и доказуемо приемлемом риске; для критичных операций требуется актуальное решение авторизационной политики.