Закрытие беспарольного доступа к PostgreSQL в Kubernetes#
В образе PostgreSQL, из которого операторы развертывают СУБД средствами платформы (managed), файл pg_hba.conf содержал правило host all all 0.0.0.0/0 trust.
PostgreSQL обрабатывает правила pg_hba.conf по первому совпадению, поэтому это правило перекрывало корректное правило md5, расположенное ниже, и оно не применялось.
В результате любой под кластера, а также любой узел с сетевым доступом к поду PostgreSQL, мог подключиться к СУБД по TCP под любой ролью, включая суперпользователя postgres, без пароля.
Пароль суперпользователя при этом задан, но правилом trust игнорировался.
Исправление внесено в образ PostgreSQL начиная с версии 2.1 и применяется автоматически только при чистой установке из этого образа.
В установках из более ранних образов, в том числе после чистой установки, а также на развернутых ранее экземплярах устранение выполняется вручную по приведенной ниже процедуре.
Важно
Обновление платформы до версии 2.1 не устраняет проблему на уже развернутых экземплярах.
Подробности приведены в описании устранения при обновлении платформы.
Кого это касается#
Процедура относится к установкам, в которых PostgreSQL развернута средствами платформы: под СУБД создается оператором как объект StatefulSet с названием вида *-postgres-15, а в манифесте компонента задан параметр type: "managed".
Как правило, в установке присутствуют два таких экземпляра, и исправить необходимо каждый из них:
общая база данных компонентов –
<deployment>-postgres-15, напримерaa-demo-postgres-15;база данных панели мониторинга –
<dashboard>-postgres-15, напримерaa-demo-dashboard-postgres-15.
Здесь:
<deployment>– название развертывания платформы, заданное в манифестах;<dashboard>– название ресурсаDashboard.
Установки с внешней СУБД PostgreSQL (type: "unmanaged") этим образом не управляются, и процедура на них не распространяется.
Проверка экземпляра#
Для проверки экземпляра выполните следующие действия:
Получите список всех экземпляров PostgreSQL, развернутых средствами платформы:
kubectl get sts -A --no-headers -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name \ | grep -- -postgres-15
Каждая строка вывода – отдельный экземпляр. Экземпляров может быть несколько: помимо основного, собственный экземпляр разворачивает панель мониторинга.
Задайте переменные окружения для проверяемого экземпляра, подставив значения из вывода предыдущей команды:
NS=<namespace> POD=<statefulset>-0
Здесь:
<namespace>– пространство имен из первого столбца вывода;<statefulset>– название объекта StatefulSet из второго столбца.
Важно
Название пода нельзя собирать из названия развертывания платформы: экземпляр панели мониторинга называется иначе. Проверку и устранение необходимо выполнить для каждой строки вывода и убедиться, что необработанных экземпляров не осталось.
Выведите действующий файл
pg_hba.conf:kubectl exec -n $NS $POD -- sh -c 'grep -nEv "^[[:space:]]*#|^[[:space:]]*$" "$(psql -tAc "SHOW hba_file")"'
Примечание
Путь к файлу необходимо получать от самого сервера с помощью запроса
SHOW hba_file, а не из переменной окруженияPGDATA: в этом образе она указывает на другой каталог.Вывод показывает файл целиком и нужен для ручной правки, а само наличие уязвимости определяет сервер:
kubectl exec -i -n $NS $POD -- psql -tA -F' | ' <<'SQL' SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method = 'trust' AND type <> 'local' ORDER BY line_number; SQL
Представление
pg_hba_file_rulesотдает правила так, как их разобрал сам сервер, поэтому запрос видит и строки, записанные в нестандартном виде, например перенесенные обратной косой чертой. Экземпляр уязвим, если запрос вернул хотя бы одну строку, например такую:Дополнительно проверьте подключение суперпользователя по TCP с заведомо неверным паролем:
PODIP=$(kubectl get pod -n $NS $POD -o jsonpath='{.status.podIP}') kubectl exec -n $NS $POD -- sh -c "PGPASSWORD=wrong psql -h $PODIP -U postgres -d postgres -tAc 'select 1'"
Ожидаемый результат:
на уязвимом экземпляре команда возвращает
1;на исправленном экземпляре команда завершается ошибкой
FATAL: password authentication failed for user "postgres".
Устранение на развернутом кластере#
Правка вносится в файл pg_hba.conf и активируется перезагрузкой конфигурации без перезапуска СУБД: соединения не разрываются, простоя нет.
Предупреждение
Перезагрузка конфигурации закрывает доступ всем ролям, у которых пароль не задан.
Для СУБД, развернутой средствами платформы, пароли заданы по умолчанию: пароли компонентов задает сценарий инициализации оператора, а пароль postgres берется из секрета.
Роль, заведенная вручную без пароля, доступ потеряет.
Правка затрагивает все правила подключений по TCP, в том числе для петлевых адресов 127.0.0.1/32 и ::1/128, если они есть в файле, поэтому после нее пароль требуется при любом подключении по TCP, включая подключение изнутри пода с параметром -h.
Команды и сценарии, которые подключаются к СУБД по названию сервиса или по адресу пода, необходимо запускать с паролем, например переданным через файл .pgpass.
Подключения через локальный сокет без параметра -h, которыми пользуются команды на этой странице, правка не затрагивает.
Для устранения проблемы выполните следующие действия для каждого экземпляра, полученного на шаге проверки:
Задайте переменные окружения:
NS=<namespace> POD=<statefulset>-0
Убедитесь, что у всех ролей с правом входа заданы пароли:
kubectl exec -n $NS $POD -- psql -tAc \ "SELECT rolname FROM pg_authid WHERE rolcanlogin AND rolpassword IS NULL"
Ожидаемый результат: пустой вывод. Если команда вернула названия ролей, задайте им пароли до перезагрузки конфигурации – иначе эти роли потеряют доступ.
Замените правила
trustдля подключений по TCP и перезагрузите конфигурацию:kubectl exec -i -n $NS $POD -- sh <<'EOF' HBA=$(psql -tAc "SHOW hba_file") || exit 2 [ -n "$HBA" ] && [ -f "$HBA" ] || { echo "ERROR: файл pg_hba.conf не прочитан"; exit 2; } cp "$HBA" "$HBA.trust-fix.bak" || { echo "ERROR: резервная копия не создана, файл не изменен"; exit 2; } sed -ri 's@^([[:space:]]*host(ssl|nossl|gssenc|nogssenc)?[[:space:]]+[^#]*[[:space:]])trust([[:space:]]*(#.*)?)$@\1md5\3@' "$HBA" psql -tAc "SELECT pg_reload_conf()" >/dev/null || { echo "ERROR: конфигурация не перезагружена"; exit 2; } echo "reload OK" psql -tA -F' | ' -c "SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method = 'trust' AND type <> 'local' ORDER BY line_number" EOF
Команда последовательно выполняет следующие действия:
прекращает работу, если не получила путь к файлу или не создала резервную копию, поэтому файл в этих случаях остается нетронутым;
сохраняет резервную копию исходного файла рядом с ним под названием
pg_hba.conf.trust-fix.bak;заменяет
trustнаmd5во всех строках подключений по TCP –host,hostssl,hostnossl,hostgssencиhostnogssenc, – включая строки с комментарием в конце;не изменяет строки
local, которые нужны для администрирования внутри пода, и не трогает текст комментариев;применяет изменения без перезапуска СУБД;
спрашивает у сервера, не осталось ли правил
trustдля подключений по TCP.
Ожидаемый результат: сообщение
reload OK, после которого запрос не вернул ни одной строки.Важно
Выражение
sedправит строки в обычной записи, а последний запрос показывает правила так, как их разобрал сервер. Поэтому строку, записанную в нестандартном виде, например перенесенную обратной косой чертой, команда не преобразует, но покажет. Исправьте такие строки вручную и повторите перезагрузку конфигурации.
Правка сохраняется при перезапуске пода по следующим причинам:
файл
pg_hba.confнаходится в каталогеPGDATAна постоянном томе PVC, поэтому правка сохраняется на диске;под создает файл
pg_hba.confзаново только при первичной инициализации, то есть на пустом томе;оператор не изменяет содержимое файла
pg_hba.conf: он управляет только объектом StatefulSet и словарем конфигурации инициализации, создающим базы данных и роли.
Устранение при обновлении до 2.1#
Обновление платформы до версии 2.1 само по себе не изменяет файл pg_hba.conf на существующих экземплярах.
Порядок устранения проблемы тот же, что и на развернутом кластере.
Причины следующие:
Исправление в образе меняет только поведение утилиты
initdb, а она отрабатывает лишь при первичной инициализации пустого тома. При обновлении том PVC сохраняется, поэтому при запуске нового образа инициализация базы данных пропускается, и прежний файлpg_hba.confостается без изменений.Мажорная версия PostgreSQL между версиями
2.0-upd2и2.1не меняется – в обеих используется PostgreSQL 15. Поэтому обновление сводится к перезапуску пода на новом образе, а конфигурация не затрагивается.При мажорном обновлении PostgreSQL конфигурацию нового кластера приводят в соответствие с прежней вручную, поэтому уязвимые правила могут быть перенесены вместе с ней. Проверку экземпляра необходимо выполнять и после такого обновления.
Таким образом, после обновления до версии 2.1 исправленный образ уже установлен, но действующий файл pg_hba.conf на ранее развернутых экземплярах не изменяется.
Автоматически исправление применяется только при чистой установке, то есть на новом томе PVC.
Рекомендуемый порядок действий при обновлении:
Выполните обновление платформы штатным способом.
Выполните устранение проблемы на всех экземплярах PostgreSQL, развернутых средствами платформы.
Выполните проверку результата.
Проверка результата#
Для проверки результата выполните следующие действия для каждого экземпляра:
Убедитесь, что у сервера не осталось действующего правила
trustдля подключений по TCP:kubectl exec -i -n $NS $POD -- sh <<'EOF' BAD=$(psql -tAc "SELECT count(*) FROM pg_hba_file_rules WHERE error IS NOT NULL") || exit 2 [ "$BAD" = "0" ] || { echo "ERROR: сервер не разобрал $BAD строк файла, проверка не выполнена"; exit 2; } ROWS=$(psql -tA -F' | ' -c "SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method = 'trust' AND type <> 'local' ORDER BY line_number") || exit 2 if [ -n "$ROWS" ]; then echo "$ROWS" echo "FAIL: остались правила trust для подключений по TCP" exit 1 fi echo "OK: правил trust для подключений по TCP не осталось" EOF
Проверка спрашивает свойство у самого сервера, а не разбирает текст файла, поэтому она видит и правила, записанные в нестандартном виде. Представление
pg_hba_file_rulesотражает содержимое файла, а не загруженную сервером конфигурацию, поэтому, если файл правили вручную, перед проверкой выполните перезагрузку конфигурации запросомSELECT pg_reload_conf(). Возможны три исхода:OK– сервер разобрал файл целиком, и правилtrustдля подключений по TCP в нем нет;FAILсо списком правил – такие правила остались, поэтому исправьте их вручную и повторите перезагрузку конфигурации;ERROR(код возврата2) – проверка не выполнена, потому что запрос не прошел или сервер не разобрал часть строк файла. Проверьте название пода и пространства имен, а также права на выполнениеkubectl exec, и повторите проверку. Отсутствие сообщенияOKнельзя трактовать как отсутствие проблемы.
Убедитесь, что беспарольное подключение суперпользователя по TCP отклоняется:
PODIP=$(kubectl get pod -n $NS $POD -o jsonpath='{.status.podIP}') OUT=$(kubectl exec -n $NS $POD -- sh -c "PGPASSWORD=wrong psql -h $PODIP -U postgres -d postgres -tAc 'select 1'" 2>&1) if printf '%s\n' "$OUT" | grep -q 'authentication failed'; then echo "OK: пароль требуется" elif printf '%s\n' "$OUT" | grep -qx '1'; then echo "FAIL: подключение без пароля прошло" else echo "ERROR: проверка не выполнена: $OUT" fi
Команда ищет результат по строкам вывода, поэтому сообщения, которые
psqlдописывает в поток ошибок, не меняют исход.Убедитесь, что компоненты платформы продолжают работать, так как они подключаются к СУБД по TCP с паролем:
Выведите поды пространства имен, которые не готовы к работе:
kubectl get pods -n $NS --no-headers \ | awk '$3 != "Completed" { split($2, r, "/"); if ($3 != "Running" || r[1] != r[2]) print }'
Команда выводит под, если он не завершил работу штатно (
Completed) и при этом не находится в состоянииRunningили готовы не все его контейнеры, например0/1в столбцеREADY. Под, у которого проба готовности не проходит из-за ошибки аутентификации в СУБД, остается в состоянииRunningс неполной готовностью, поэтому команда его выводит.Ожидаемый результат: пустой вывод.
Найдите в журналах подов ошибки аутентификации в СУБД:
for p in $(kubectl get pods -n $NS -o name --field-selector=status.phase=Running); do kubectl logs -n $NS "$p" --all-containers --since=15m 2>/dev/null \ | grep -iE 'password authentication failed|no password supplied' \ | sed "s|^|$p: |" done
Команда просматривает журналы за последние 15 минут, поэтому, если перезагрузка конфигурации выполнена раньше, увеличьте значение параметра
--since.Ожидаемый результат: пустой вывод.
Возможны следующие исходы:
оба вывода пустые – компоненты работают с паролями, и правку можно оставить;
первая команда вывела поды, а вторая ничего не нашла – поды еще запускаются, поэтому дождитесь их готовности и повторите обе команды;
вторая команда нашла ошибки аутентификации – компонент подключается без пароля или с неверным паролем, поэтому выполните откат и задайте пароль роли, указанной в сообщении об ошибке, прежде чем повторять процедуру.
Откат#
Изменение применяется перезагрузкой конфигурации, без перезапуска СУБД и без разрыва соединений, поэтому простоя нет.
Резервная копия исходного файла сохраняется рядом с ним под названием pg_hba.conf.trust-fix.bak.
Для отката изменений выполните следующую команду:
kubectl exec -i -n $NS $POD -- sh <<'EOF'
HBA=$(psql -tAc "SHOW hba_file") || exit 2
[ -n "$HBA" ] || { echo "ERROR: путь к файлу не получен, откат не выполнен"; exit 2; }
[ -f "$HBA.trust-fix.bak" ] || { echo "ERROR: резервная копия не найдена, откат не выполнен"; exit 2; }
cp "$HBA.trust-fix.bak" "$HBA" || { echo "ERROR: файл не восстановлен"; exit 2; }
psql -tAc "SELECT pg_reload_conf()" >/dev/null || { echo "ERROR: файл восстановлен, но конфигурация не перезагружена"; exit 2; }
echo "rollback OK"
EOF
Сообщение rollback OK команда печатает только после восстановления файла и перезагрузки конфигурации.
Каждый предыдущий шаг прекращает работу с кодом возврата 2, поэтому отсутствие резервной копии команда сообщает как ошибку, а не как успех.
Этот путь нужен, когда компоненты уже не поднялись, и администратору здесь важен верный ответ.
Дополнительные замечания:
Метод
md5в файлеpg_hba.confсовместим с паролями, которые хранятся в форматеscram-sha-256: при таком пароле PostgreSQL автоматически применяет механизм SCRAM. Поэтому существующие пароли компонентов продолжают работать без изменений.В качестве дополнительной меры защиты рекомендуется ограничить сетевой доступ к поду PostgreSQL с помощью объекта NetworkPolicy.