Минимальные привилегии на практике
Минимальные привилегии — принцип, который все цитируют и почти никто не удерживает. Деградация — это расползание прав: гранты копятся, ничего не отзывается. Лекарство операционное: deny by default, доступ точно вовремя и цикл отзыва.
Ты открываешь IAM-политику, привязанную к главной сервисной роли твоей команды, чтобы отладить ошибку прав, — и вот оно: s3:* на Resource: "*". Никто не решал, что биллинговый воркер должен уметь удалить любой бакет в аккаунте — оно просто так получилось. Полтора года назад кому-то понадобился доступ на чтение к одному бакету, он упёрся в deny, нашлёпал на него wildcard, чтобы разблокировать релиз, и пошёл дальше. Грант пережил и задачу, и человека, и причину. Теперь эта роль в одной утёкшей утечке ключа от того, чтобы стереть боевое хранилище, — и самое страшное, что всё работает идеально. Минимальные привилегии не упали с грохотом. Они истёрлись, один удобный грант за раз.
К концу этого урока ты поймёшь, почему минимальные привилегии деградируют в избыточные, и три операционных хода — deny by default, доступ точно вовремя и цикл отзыва, — которые на самом деле удерживают линию.
Принцип — и почему его недостаточно просто провозгласить
Принцип минимальных привилегий гласит: каждый субъект — пользователь, сервис, процесс — должен держать ровно те права, что нужны ему для работы, и ни одним больше. NIST называет его фундаментом управления доступом; OWASP Authorization Cheat Sheet открывается словами «enforce least privileges» и «deny by default». Каждый инженер согласно кивает. Почти ни в одной боевой системе этого на самом деле нет.
Причина в том, что минимальные привилегии — это не свойство, которое настраивают один раз, а свойство, которое приходится поддерживать против постоянного давления. Права липки в одну сторону. Выдать легко: кто-то заблокирован, ты добавляешь allow, тикет закрывается, сегодня все довольны. Отозвать тяжело: тебя никто не блокирует правом, которое у тебя всё ещё есть, поэтому нет тяги его убрать, нет красного теста, нет алерта с пейджера. Система, которая выдаёт по запросу и никогда не отзывает, не дрейфует к минимальным привилегиям — она монотонно дрейфует от них. Вся проблема в этой асимметрии, и у неё есть имя.
Расползание прав: медленная течь
Расползание прав (privilege creep) — это постепенное накопление прав, которые субъекту больше не нужны. Оно приходит из горстки совершенно будничных источников, ни один из которых в момент не ощущается как решение по безопасности:
- Смена роли, которая добавляет, не вычитая. Инженер переходит из команды платежей в платформу. Доступ группы платформы он получает в первый же день. А доступ к платежам? Он всё ещё на месте через год — никто не владеет вычитанием, а отозвать его может что-то сломать, поэтому он остаётся.
- Удобный wildcard.
denyблокирует релиз в 16:00. Уезжает в прод фиксs3:*/Resource: *, потому что сузить его до трёх реально нужных действий и одного бакета — это двадцать минут, которых у тебя нет. Wildcard постоянен; дедлайн был временным. - Постоянный админ. Break-glass-доступ для инцидента в 2 ночи так и не отзывают на следующее утро. Теперь это просто «доступ, который у меня есть», а через три месяца он несущий для какой-то рутинной задачи, для которой никогда не предназначался.
- Скопированная роль. Новому сервису нужны права? Клонируй роль у соседнего сервиса. Теперь он наследует каждый грант, который тот сервис нарастил, включая те, которые никто не может объяснить.
Каждый шаг локально рационален и глобально разъедающ. В итоге — флот идентичностей, каждая из которых держит куда больше власти, чем требует её реальная работа, а значит, радиус поражения любой одной скомпрометированной учётки огромен и полностью невидим до того дня, когда его эксплуатируют.
Deny by default: сделать «нет» точкой старта
Структурный фикс расползания начинается с фундамента — deny by default. Авторизация начинается с отказа, а доступ выдаётся только явным положительным правилом, совпадающим по субъекту, действию и ресурсу. Всё, что не разрешено явно, — запрещено, включая, что критично, всё новое.
Это звучит очевидно, пока не сопоставишь это со сбоем, который оно предотвращает. Система allow-by-default (или любой allowlist с wildcard-фолбэком) отказывает «открыто» (fails open): добавь новый эндпоинт, новый тип ресурса, новое админ-действие — и оно достижимо, если кто-то не вспомнил написать для него deny-правило. Deny by default отказывает «закрыто» (fails closed): новая штука недостижима, пока кто-то не напишет allow. Разница в том, утекает ли забытое правило в доступ или просто выдаёт 403, по которому кто-то заводит тикет. Fail-closed превращает твои упущения в неудобства, а не в пробои.
На практике это значит: никаких Resource: "*" и никаких action: * в политиках, где их можно избежать; права, ограниченные конкретными действиями на конкретных ресурсах; и ответ по умолчанию на «может ли эта идентичность сделать эту штуку» — нет, пока правило не скажет да. Цена реальна — ты пишешь больше узких правил и ешь больше 403 во время разработки. Эта цена и есть смысл. Это и есть то трение, что держит wildcard снаружи.
▸Почему это работает
Почему не аудитить права раз в квартал и не вычищать избыток? Потому что периодический аудит борется с асимметрией, которую не может выиграть. Гранты накапливаются непрерывно — каждый день, под реальными дедлайнами, — а вычистка происходит пакетом четыре раза в год и не является ничьей любимой работой. Хуже того, нельзя безопасно убрать право, про которое нельзя доказать, что оно не используется, а доказать отрицание («ничто на это не опирается») по живой системе по-настоящему трудно, поэтому аудиты склоняются к тому, чтобы оставлять. Аудит не бесполезен, но это швабра, а не кран. Устойчивый фикс меняет то, как гранты выдаются — короткоживущими и самоистекающими, — чтобы избыток вовсе не накапливался.
Доступ точно вовремя: гранты, которые истекают сами
Асимметрия, которая гонит расползание, — это «легко выдать, тяжело отозвать». Доступ точно вовремя (JIT, just-in-time) атакует её напрямую, делая отзыв автоматическим: вместо того чтобы держать постоянное право вечно, субъект запрашивает повышенный доступ под конкретную задачу, получает его на ограниченное окно, и система забирает его назад, когда окно закрывается, — никому не приходится помнить.
Паттерн всплывает на каждом слое:
- Люди-админы: постоянное членство в роли администратора убирается; инженер запрашивает повышение (часто с причиной и согласованием) и получает его, скажем, на 60 минут, после чего членство в роли автоматически снимается. Состояние по умолчанию каждого человека — не привилегированное.
- Сервисы: вместо долгоживущего API-ключа с широким scope, зашитого в деплой, нагрузка ассумит роль и получает короткоживущие учётки (минуты — час), которые авто-ротируются и авто-истекают. Утёкшая учётка полезна только внутри своего крошечного окна.
- Break-glass: аварийный доступ сам является JIT-грантом — явно запрашивается, ограничен по времени, громко логируется и авто-отзывается, — поэтому инцидент в 2 ночи не оставляет за собой постоянный админ-грант.
JIT переворачивает устойчивое состояние. С постоянным доступом по умолчанию «у всех больше, чем нужно, всё время», и ты тратишь усилия на удаление избытка, который никогда полностью не выловишь. С JIT по умолчанию «ни у кого ничего нет, пока он не попросит», и привилегия существует только те минуты, что реально используется. Ты больше не борешься с расползанием уборкой — ты убрал поверхность, на которой расползание накапливается.
У сервисной роли стоит `s3:*` на `Resource: *`, потому что кто-то добавил wildcard 18 месяцев назад, чтобы разблокировать релиз. Сервис на деле когда-либо вызывает только GetObject и PutObject на одном бакете. Какой фикс правильный?
Как зрелые инженеры удерживают линию
Удержание минимальных привилегий — это операционный цикл, а не разовая настройка. Зрелые ходы: сделать deny дефолтом, чтобы новая поверхность отказывала закрыто; делать гранты узкими (конкретные действия на конкретных ресурсах, без рефлекторных wildcard) и короткоживущими (JIT с жёстким истечением), чтобы привилегия существовала только пока используется; и замкнуть цикл отзывом — когда кто-то меняет команду, когда проект заканчивается, когда срабатывает break-glass, вычитанием должен кто-то владеть, а не оставлять его квартальному аудиту, который склоняется к тому, чтобы оставлять. Грант без истечения и без владельца — это тот самый, что станет инцидентом s3:* в следующем году.
Почему расползание прав почти всегда заставляет права со временем расти, а не сжиматься, даже в хорошо организованных командах?
В чём ключевое преимущество по безопасности у доступа точно вовремя перед постоянным админ-грантом?
Упорядочь эти шаги, чтобы исправить избыточную сервисную роль без простоя, от первого к последнему:
- 1 Понаблюдать, какие действия/ресурсы роль реально использует (логи доступа / IAM access analyzer)
- 2 Написать новую политику, ограниченную ровно этими наблюдёнными действиями на этих конкретных ресурсах
- 3 Выкатить суженную политику и следить за deny-ошибками / 403 в staging и канарейке
- 4 Заменить долгоживущий ключ на короткоживущие assumed-role учётки
- 5 Добавить владельца и дату истечения/пересмотра, чтобы грант не стал тихо снова постоянным
- 01Объясни, почему минимальные привилегии со временем истираются в избыточные, и назови механизм.
- 02Что именно чинят deny-by-default и доступ точно вовремя, и почему JIT — более устойчивое лекарство?
Минимальные привилегии — это принцип, который все цитируют и почти никто не удерживает, потому что это не настройка, которую конфигурируют один раз, а свойство, которое поддерживают против постоянного давления. Выдачу тянет реальный, немедленный блокер; у отзыва нет такой же тяги, поэтому права монотонно дрейфуют вверх. Эта деградация и есть расползание прав, питаемое сменами ролей, что добавляют не вычитая, удобными wildcard, отгруженными ради дедлайна, неотозванным break-glass и скопированными ролями. У операционного лекарства три хода. Deny by default делает так, что авторизация начинается с «нет», поэтому новая поверхность отказывает закрыто — забытое правило даёт 403, а не пробой. Узкие, короткоживущие гранты ограничивают права конкретными действиями на конкретных ресурсах. А доступ точно вовремя делает отзыв автоматическим: повышение запрашивается под задачу, держится на ограниченное окно и снимается, когда оно закрывается, поэтому состояние по умолчанию каждой идентичности непривилегированное, а утёкшая учётка ничего не стоит через минуты. Теперь, когда ты видишь s3:* на Resource: *, твой первый вопрос: кто владеет вычитанием, и когда этот грант истекает?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.