Minecraft for Everypony Дружбомагия в каждом кубике!

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, и не привязываться к какому-то одному окружению. Там, где это необходимо, специфичные ключи, адреса сервисов и прочее мы решили сделать легко конфигурируемыми.