Раздел 37 · Redis с нуля

Безопасность и мониторинг

junior~30 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы разобрались, куда уходит память. Это последний урок раздела, и он про две вещи, без которых Redis нельзя выпускать в продакшен: как закрыть его от чужих и как заметить беду раньше пользователей. Основы мы видели в обзоре продакшена: не публиковать порт, пароль, первый пользователь ACL, INFO stats и SLOWLOG. Теперь подробно.

Склад без замка

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

Redis и есть такой склад. Он задуман для работы во внутренней сети, где ему доверяют все клиенты. Команды вроде FLUSHALL, CONFIG SET и DEBUG это погрузчик: они меняют что угодно, и Redis не спрашивает, кто ты, если ты сам этого не настроил.

Что бывает с открытым портом

Сканеры обходят весь интернет и ищут открытый порт 6379. Найдя Redis без пароля, они делают одно из трёх:

  • Стирают данные и оставляют ключ с требованием выкупа. Платить бесполезно: данные никто не копировал.
  • Ставят майнер. Черви, которые размножаются через открытые серверы Redis, появляются регулярно, например P2PInfect в 2023 году.
  • Захватывают машину. Через CONFIG SET можно поменять каталог и имя файла, куда Redis сохраняет снимок RDB. После этого SAVE записывает файл туда, куда скажет злоумышленник, например рядом с ключами SSH. Так доступ к Redis превращается в доступ к серверу целиком.

Последний пункт объясняет, почему приложению никогда не нужны права на CONFIG, SAVE и DEBUG. Защита строится слоями: сеть, пароль и пользователи, права на команды, шифрование.

Слой первый: сеть

Настройка bind задаёт, на каких адресах Redis принимает подключения. Ставь только внутренние адреса: bind 127.0.0.1 для сервера на той же машине или адрес во внутренней сети. В официальном образе Docker Redis слушает все адреса, потому что сеть контейнера и так изолирована:

127.0.0.1:6379> CONFIG GET bind
1) "bind"
2) "* -::*"
127.0.0.1:6379> CONFIG GET protected-mode
1) "protected-mode"
2) "no"

Защищённый режим страхует тех, кто забыл настроить и bind, и пароль: тогда Redis отвечает только локальным клиентам. В образе Docker он выключен, поэтому главная защита там одна: не публиковать порт через -p 6379:6379 и держать контейнер во внутренней сети.

Слой второй: пароль и пользователи

requirepass из урока про установку задаёт пароль пользователю default. Это один ключ на всех: приложение, отчёты и администратор входят под одним именем с одинаковыми правами. С Redis 6 есть ACL, и у каждого клиента может быть свой пользователь.

Заведём двух. Магазину можно читать и писать только кэш и нельзя опасные команды. Отчётам можно читать всё, но не писать:

127.0.0.1:6379> ACL SETUSER shop on >s3cret ~cache:* +@read +@write -@dangerous
OK
127.0.0.1:6379> ACL SETUSER reporter on >r3port ~* +@read -@dangerous
OK
127.0.0.1:6379> ACL LIST
1) "user default on nopass sanitize-payload ~* &* +@all"
2) "user reporter on sanitize-payload #ccece476d0df557a2d5f5febaf2a2a997031fb4b9c48982685fe1bd22609d3f2 ~* resetchannels -@all +@read -@dangerous"
3) "user shop on sanitize-payload #1ec1c26b50d5d3c58d9583181af8076655fe00756bf7285940ba3670f99fcba0 ~cache:* resetchannels -@all +@read +@write -@dangerous"

Разберём правило по частям:

  • on включает пользователя, >s3cret задаёт пароль. В ACL LIST вместо пароля хранится его хеш SHA-256.
  • ~cache:* разрешает ключи по шаблону. Остальные ключи пользователь не видит.
  • +@read +@write разрешает категории команд, -@dangerous запрещает опасные. Порядок важен: правила применяются слева направо.

Список опасных команд показывает ACL CAT dangerous. В нём FLUSHALL, KEYS, CONFIG, DEBUG, SAVE, MONITOR, SHUTDOWN и ещё десятки команд:

127.0.0.1:6379> ACL CAT dangerous
...
20) "shutdown"
21) "migrate"
22) "info"
23) "flushall"
...
49) "keys"
...

Обрати внимание: туда попал и INFO. Если система мониторинга ходит под отдельным пользователем, INFO ей придётся разрешить явно.

Права можно проверить, не входя под пользователем. ACL DRYRUN отвечает, пропустил бы Redis команду:

127.0.0.1:6379> ACL DRYRUN shop GET cache:book:1
OK
127.0.0.1:6379> ACL DRYRUN shop GET user:1:email
"User shop has no permissions to access the 'user:1:email' key"
127.0.0.1:6379> ACL DRYRUN shop FLUSHALL
"User shop has no permissions to run the 'flushall' command"
127.0.0.1:6379> ACL DRYRUN reporter SET cache:book:1 x
"User reporter has no permissions to run the 'set' command"

Теперь войдём под магазином:

127.0.0.1:6379> AUTH shop s3cret
OK
127.0.0.1:6379> GET cache:book:1
"{\"id\":1}"
127.0.0.1:6379> GET user:1:email
(error) NOPERM No permissions to access a key
127.0.0.1:6379> CONFIG SET dir /tmp
(error) NOPERM User shop has no permissions to run the 'config|set' command
127.0.0.1:6379> KEYS *
(error) NOPERM User shop has no permissions to run the 'keys' command
127.0.0.1:6379> ACL WHOAMI
(error) NOPERM User shop has no permissions to run the 'acl|whoami' command

Тот самый трюк с CONFIG SET больше не пройдёт. Последняя строка показывает обратную сторону: даже безобидный ACL WHOAMI не входит в @read и @write, поэтому его пришлось бы разрешить отдельно через +acl|whoami.

Каждый отказ Redis записывает в журнал отказов. Его читает ACL LOG под пользователем с правами администратора:

127.0.0.1:6379> ACL LOG 1
1)  1) "count"
    2) (integer) 1
    3) "reason"
    4) "command"
    5) "context"
    6) "toplevel"
    7) "object"
    8) "keys"
    9) "username"
   10) "shop"
   11) "age-seconds"
   12) "0"
   13) "client-info"
   14) "id=25 addr=127.0.0.1:45130 laddr=127.0.0.1:6379 fd=10 name= age=0 idle=0 flags=N db=0 ..."
...

Если в журнале внезапно появились сотни отказов, это либо сломанный деплой, либо кто-то пробует ключи.

Пользователи, созданные через ACL SETUSER, живут до перезапуска. Чтобы они пережили его, их описывают в файле aclfile и сохраняют командой ACL SAVE. Старый способ прятать опасные команды, rename-command FLUSHALL "" в конфиге, ещё работает, но документация Redis советует вместо него ACL.

урок из курса

Урок из курса «Redis с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.