Blameless Postmortem: Сбой в работе 26-28 января
Что случилось?
В пятницу и субботу сервер был недоступен для подключения из-за инфраструрных ошибок, диагностика и починка которых заняла много времени.
Вводные данные
После переезда на новый сервер случайные игроки начали обращаться со странными и невоспроизводимыми ошибками доступа к серверу:
- невозможность скачать сборку;
- проблемы с обновлением списка сборок;
- проблемы с авторизацией
- …и так далее
Первичная диагностика
Логов ошибок, или записей в firewall не было, что исключало возможность лёгкой диагностики. Спустя какое-то время случайно
обнаружилось, что IP, который присвоен CDN-серверу, внесен в список РКН.
Возможности сменить его в CDN нет, поэтому на время было решено отказаться от CDN и пускать трафик напрямую. Однако, на него была завязана часть инфраструктуры (TLS-сертификаты), и вместо транзитных (на пути от сервера к CDN) мы использовали Let's Encrypt.
Эскалация проблемы
Сразу после переключения проблемы с лаунчером исправились, однако нарушилась связь с внутренним сервером авторизации.
Несмотря на то, что он работал и был доступен с сети, с самого сервера к нему нельзя было обратиться. В логах была только одна строчка:
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException:
PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
Вкратце это значило, что какой-то из сертификатов в цепочке доверия, ведущей к auth.mc4ep.ru
не доверен. Такая проблема возникала в прошлом и в ранних версиях JRE, когда корневой сертификат Let's Encrypt ещё не был внесен в доверенные.
В то же время, тестовая программа, призванная обнаружить проблемы с cacerts
, отрабатывала успешно.
import java.net.URL;
import javax.net.ssl.SSLHandshakeException;
public class SSLExample {
public static void main(String[] args) throws IOException {
URL url = new URL("https://auth.mc4ep.ru/session/minecraft/join");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
try {
connection.connect();
System.out.println("Headers of " + url + " => " + connection.getHeaderFields());
} catch (SSLHandshakeException e) {
System.out.println("Untrusted: " + url);
}
}
}
Причина проблемы
Только подключившись в режиме отладки к работающему тестовому серверу мы обнаружили, что «внутри» процесса PKIXValidator
отвергает даже заведомо рабочие сертификаты, к примеру, от google.com
, а некоторые, точно такие же рабочие — принимает.
Дальнейшее исследование показало, что системный /usr/lib/jvm/java-8-oracle/jre/lib/security/cacerts
совсем не используется для построения. Вместо него использовалось встроенное хранилище сертификатов — артефакт разработки, оставшийся с 2015 года. По факту это оказался устаревший системный cacerts
с добавленным корневым сертификатом StarSSL
, на текущий момент уже отозванный и не использующийся.
В нем, конечно же, корневых сертификатов Let's Encrypt не было
Решение
Быстрым исправлением послужила замена этого хранилища на системное. Полноценным — пересборка части кода, чтобы исключить саму возможность использования устаревших хранилищей сертификатов.
Выводы
Разрыв цепочек передачи знаний и опыта между разработчиками привел к наличии устаревших артефактов в кодовой базе, предназначения которых уже никто не знал. Такие артефакты работали успешно лишь в одном окружении, а с его сменой принесли трудноуловимые ошибки.
Чтобы исключить возможность таких ошибок в будущем мы приняли решение взять под систему контроля версий наши «in-house» патчи к кодовой базе Minecraft, и не привязываться к какому-то одному окружению. Там, где это необходимо, специфичные ключи, адреса сервисов и прочее мы решили сделать легко конфигурируемыми.