The Gluu Affair

The Gluu Affair

“Оно работало” - два слова, после которых начинается самое интересное.
Никто не смотрел внутрь два года. Незачем было.
Я посмотрел. Там жил призрак уволенного коллеги.
Он ушёл. Скрипты - остались.

Пролог

Есть системы, которые просто работают. Не трогаешь - не ломается. Gluu был такой системой.

Сервер аутентификации. Три критических приложения висели на нём. Сертификаты обновлялись сами. Пользователи входили сами. Я получил доступ при онбординге и благополучно забыл о его существовании.

Незачем было помнить.


Акт I: Клиент написал

Раннее утро, первый кофе. Slack открыт, но ничего не жду.

Клиент написал: Gluu не работает.

Не алерт. Не автоматика. Живой человек написал в чат. Это значит: всё, что должно было сработать до него - не сработало.

Gluu был жив. Но сертификат истёк. А система, которая должна была его обновить - t2.micro инстанс - существовала в зомби-состоянии. Включена, но недоступна. Ни SSH, ни SSM, ни консоль. Просто висела.

И тут выяснилось, что SSH-ключ от этого инстанса мне при онбординге не передали. Не сказали что он есть. Не сказали где лежит. Тэги на инстансе стояли - owner = company - но ключ не нашёлся ни в документации, ни в секретах. Пришлось тыкать коллег и начальство. Это заняло время. Сертификат оставался недействительным.

Ключ нашли. Подключиться к инстансу всё равно не удалось - он был не совсем мёртв, но точно не жив.

Сделал снапшот. Поднял новый инстанс - со своим ключом и SSM. Подключился. Вручную запустил крон. Сертификат обновился. Gluu заработал. За окном успело стемнеть.

Проблема решена. И вот теперь - пока тихо - я решил посмотреть, на что именно нажал.


t2.micro. Отдельная виртуальная машина, работает круглосуточно. Единственная задача - раз в сутки запустить крон, который обновит сертификат. Certbot на выделенном инстансе. За который платили каждый месяц.

Кандидат на улучшение.

У меня был опыт похожей задачи - Let’s Encrypt в AWS China, где бесплатные ACM-сертификаты недоступны. Там тоже решили не через выделенный инстанс, а через Lambda: запускается по расписанию, делает дело и выключается - никакого idle, никакого сервера на поддержке. Я взял ту архитектуру и переписал под текущую ситуацию: EventBridge раз в сутки запускает проверку, DynamoDB хранит маппинг домен → сертификат, если срок истекает - Lambda получает новый сертификат от Let’s Encrypt, импортирует в ACM, кладёт в S3.

Архитектура для одного сертификата. Оверкилл? Да. Зато прозрачно, масштабируемо, и не требует круглосуточного инстанса.

Напоминание в календарь - проверить через 60 дней, когда придёт время обновления.

Вроде всё работает.


Акт II: 60 дней спустя - призрак

Напоминание сработало. Проверяю.

Сертификат в ACM - обновлён. В S3 - обновлён. ALB использует новый.

Gluu использует старый.

Полез в документацию - нашёл инструкцию: “делай раз, делай два, получишь результат”. Почему так - не написано. Зачем - не написано.

Пошёл смотреть Lambda, которая должна была обновить Gluu. Нашёл адрес - она подключалась по SSH к tls_renew_instance. К тому самому инстансу. Который я выключил два месяца назад.

Новая архитектура выпускала сертификат исправно. И передавала его призраку.

Хорошо что я не удалил тот инстанс из снапшота - который поднимал для быстрого фикса. Включил. Нашёл скрипт. Открыл.

Цепочка:

s3 event → lambda → ssh retired_colleague@tls_renew → ssh retired_colleague@gluu → sudo gluu.service → renew.sh  

retired_colleague. Пользователь, который давно ушёл из компании. Его home-директория на сервере осталась. Его SSH-ключи - актуальны. Его скрипт - всё ещё запускается каждые 60 дней.

Я открыл renew.sh. Двенадцать операций бэкапа до того, как скрипт делал что-то полезное. Бэкапы писались в ту же папку, которую бэкапили. Проверок на ошибки - ноль. Верификации результата - ноль. Каждые 60 дней /etc/certs/ обрастала двенадцатью новыми слоями, как кольца на дереве.

Кто-то очень боялся потерять файл. И совсем не боялся уронить прод.

Исправил цепочку - убрал промежуточный хоп, Lambda теперь идёт напрямую на Gluu. Заменил SSH-ключи. Исправил пути бэкапов. Прогнал скрипт - не сработало. Проверил логи, убедился что права нужные, уточнил наличие всех файлов. Прогнал снова - сертификат обновился.

Поставил напоминание ещё на 60 дней.

Вроде работает. Вроде.


Акт III: Java хранит секреты

Напоминание сработало.

Сертификат в ACM - свежий. В S3 - свежий. В Gluu - старый. Опять.

Но в этот раз я не стал шпаклевать трещину.

Прошёл по цепочке до конца: s3 → lambda → gluu → скрипт → локальная копия. Копия была правильная. Свежий сертификат лежал на диске. Gluu его не видел.

Залез в скрипт с фонариком.

Apache - PID-файл не удалялся при рестарте, сервис не поднимался. Починил. Запустил. Gluu всё равно не верит сертификату.
Самоподписанный - обновляется.
Права на файлы - чисто.
Fullchain собран корректно.
Keytool импортировал - прошёл.

Сервер утверждает, что всё сделал правильно. И не работает.

Добавил паузы между стартом сервисов. Добавил проверку что процесс реально поднялся. Запустил.

Не работает.

Встал. Налил воды. Сел обратно.

Открыл логи. Километры Java stacktrace. Где-то среди них - одна строка, которая имела значение:

PKIX path validation failed: CertPathValidatorException: timestamp check failed  

Java не верила сертификату. Считала его истёкшим. Я точно знал, что импортировал свежий в keystore. В правильный keystore, в три alias’а - external_idp, oxauth_ssl, gluu_openldap - зачем их три, я так и не выяснил, но обновил все на всякий случай.

Keytool говорил что импорт прошёл. Java говорила что сертификат старый. Кэш? Порядок загрузки? Я не знал точно. Но знал что ответ где-то в логах.

Перечитал. Медленно - не ошибку искал, ошибка была понятна. Искал порядок. Хронологию. Кто раньше, кто позже.

И увидел - не ответ, только след. Но след был.

Не сразу понял что вижу. Потом понял. Стало немного неприятно - не от сложности, а от того, насколько это было нелогично. Система проигнорировала мои исправления. Просто проигнорировала.

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

Я сделал запуск упорядоченным. Детерминированным. Правильным.

Второй баг перестал компенсировать первый. Система упала.

Это называется классика.


Обойтись без даунтайма не получилось. Я честно пробовал.

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

Я переписал скрипт заново. Проверка на каждом шаге. Логирование. Ожидание после каждого запуска. Проверка что java-процессов ровно четыре. Тест OAuth endpoint - HTTP 400 без токена означает “работает”.

И отдельный debug-скрипт. И документация - не “делай раз, делай два”, а с объяснением что, зачем и почему именно так.


Финал

Договорился о даунтайме заранее. Назначил на нерабочее время. Предупредил клиента.

Подгадывать под расписание EventBridge не стал - вручную запустил цепочку. Система должна была отработать сама. Но я хотел это видеть.

Терминал пошёл.

Ventilator: домен найден, срок истекает. Issuer запущен.
Issuer: Let’s Encrypt ответил. Сертификат получен. ACM обновлён. S3 обновлён.
S3 event: Lambda получила триггер. SSH на Gluu.
Скрипт: стоп. Чистка PID-файлов. Импорт в keystore. Старт в правильном порядке. Пауза. Java-процессов - 4/4.
Проверка OAuth endpoint: HTTP 400.

Всё. Зелёные галочки и тишина в терминале - хорошая тишина.


Теперь раз в 60 дней система обновляет сертификат. Независимый мониторинг проверяет срок действия - если до истечения меньше 14 дней, приходит алерт. Не письмо об успехе, которое перестаёшь замечать через полгода. Сигнал о проблеме, который требует действия.

Gluu 3.1.8 всё ещё работает. Обновить нельзя - лицензия изменилась, breaking changes такие, что это не апгрейд, а новая установка с ручной миграцией. Клиент решил постепенно перейти на другое решение. Когда-то.

Но сейчас система больше не тайна. Есть короткий ранбук - что делать если сломалось. Есть полное описание - как устроено и почему именно так. Есть debug-скрипт. Следующему инженеру не придётся начинать с нуля.

Оно работает.

Теперь я знаю почему.