The Daily Newsstand · Free, Always
Monday, September 21, 2026

Как плагин обновлений Tauri ломает HTTPS на ALT Linux

Translate

В апреле я рассказывал здесь, зачем пишу ещё один настольный клиент для S3. В комментариях справедливо заметили, что технических деталей там не было. Исправляюсь, вот одна история изнутри. Клиент написан на Tauri 2 и Rust.

При проверке на российских ОС я наткнулся на странную ошибку. Приложение запускается, первое подключение к хранилищу по HTTPS проходит, а через несколько секунд все следующие запросы падают с ошибкой проверки сертификата. На Ubuntu, Debian и МСВСфере всё работает. Если отключить проверку обновлений, всё работает и на ALT.

Виноват оказался плагин tauri-plugin-updater, который меняет переменные окружения процесса. Разбираю подробно, потому что это касается любого приложения на Tauri 2, где есть этот плагин и TLS на rustls.

Как это выглядит

Система ALT Linux p11, пакет rpm, в приложении включена автоматическая проверка обновлений. Первое подключение к хранилищу по HTTPS успешно. После проверки обновлений новые TLS-соединения падают с invalid peer certificate: UnknownIssuer. Если перед запуском вручную задать SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt, ошибки нет.

Где лежат сертификаты в разных дистрибутивах

Система

Файл с корневыми сертификатами

Каталог /etc/ssl/certs

Debian, Ubuntu, Astra

/etc/ssl/certs/ca-certificates.crt

есть, с отдельными сертификатами

МСВСфера 10.1 (RHEL 10)

/etc/pki/tls/certs/ca-bundle.crt

есть, с отдельными сертификатами

ALT Linux p11

/etc/pki/tls/certs/ca-bundle.crt

нет

Файла /etc/ssl/certs/ca-certificates.crt нет ни в МСВСфере, ни в ALT. Разница в том, что в МСВСфере сертификаты всё равно находятся через каталог, а в ALT нет ни файла, ни каталога.

Что делает плагин

Я смотрел tauri-plugin-updater версии 2.10.1, в основной ветке репозитория tauri-apps/plugins-workspace код тот же. В начале метода Updater::check() есть такой блок.

// Set SSL certs for linux if they aren't available.
#[cfg(target_os = "linux")]
{
    if std::env::var_os("SSL_CERT_FILE").is_none() {
        std::env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt");
    }
    if std::env::var_os("SSL_CERT_DIR").is_none() {
        std::env::set_var("SSL_CERT_DIR", "/etc/ssl/certs");
    }
}

Задумка понятная, помочь HTTP-клиенту найти сертификаты. Только пути взяты из Debian, а переменные меняются для всего процесса и насовсем. Если приложение проверяет обновления при запуске, то после этого вызова любая библиотека, которая читает SSL_CERT_FILE и SSL_CERT_DIR, видит путь из Debian.

Почему ломается rustls

Системные корневые сертификаты для rustls обычно загружает крейт rustls-native-certs. С версии 0.8 функция load_native_certs() устроена так.

pub fn load_native_certs() -> CertificateResult {
    let paths = CertPaths::from_env();
    match (&paths.dirs, &paths.file) {
        (v, _) if !v.is_empty() => paths.load(),
        (_, Some(_)) => paths.load(),
        _ => platform::load_native_certs(),
    }
}

Если задана хотя бы одна из переменных, сертификаты берутся только оттуда, а обычный поиск по известным путям через openssl-probe уже не выполняется. После вызова плагина на ALT SSL_CERT_FILE указывает на файл, которого нет, и SSL_CERT_DIR указывает на каталог, которого тоже нет. Хранилище корневых сертификатов получается пустым, и rustls отвергает любое TLS-соединение с ошибкой UnknownIssuer.

До проверки обновлений переменных нет, openssl-probe находит /etc/pki/tls/certs/ca-bundle.crt, и всё работает. Поэтому первое подключение и проходит.

В МСВСфере 10.1 файла тоже нет, но в каталоге /etc/ssl/certs лежат сертификаты с именами-хешами, и они загружаются. Там ошибка видна только как сообщение о неудачном чтении файла.

Есть и вторая проблема. std::env::set_var в многопоточной программе небезопасна, в редакции Rust 2024 её пометили как unsafe как раз потому, что одновременное чтение окружения из другого потока может привести к неопределённому поведению. А check() выполняется в асинхронной среде, где в это же время другие потоки открывают соединения.

Как воспроизвести

Понадобится Docker. Tauri для проверки не нужен, достаточно повторить то, что делает плагин.

fn main() {
    let before = rustls_native_certs::load_native_certs();
    println!("до: {} сертификатов", before.certs.len());

    std::env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt");
    std::env::set_var("SSL_CERT_DIR", "/etc/ssl/certs");

    let after = rustls_native_certs::load_native_certs();
    println!("после: {} сертификатов, ошибки: {:?}", after.certs.len(), after.errors);
}
docker run --rm -it alt:p11 bash
apt-get update && apt-get install -y ca-certificates

В базовом образе alt:p11 пакета ca-certificates нет, поэтому его нужно поставить перед запуском. Вот что получилось у меня.

Система

До

После

Debian 12

150 сертификатов

150, без ошибок

МСВСфера 10.1

150 сертификатов

150 и ошибка чтения ca-certificates.crt

ALT Linux p11

121 сертификат

0, файл и каталог не найдены

В ALT после двух вызовов set_var приложение остаётся без единого корневого сертификата.

Как исправить у себя

Плагин ставит пути, только если переменные ещё не заданы. Значит, достаточно задать их самому, раньше него и правильно. Найти правильные пути помогает тот же openssl-probe, которым rustls-native-certs пользуется по умолчанию.

[target.'cfg(target_os = "linux")'.dependencies]
openssl-probe = "0.2"
#[cfg(target_os = "linux")]
fn pin_system_ca_paths() {
    let found = openssl_probe::probe();
    if std::env::var_os("SSL_CERT_FILE").is_none() {
        if let Some(file) = found.cert_file.as_ref() {
            std::env::set_var("SSL_CERT_FILE", file);
        }
    }
    if std::env::var_os("SSL_CERT_DIR").is_none() {
        if let Some(dir) = found.cert_dir.first() {
            std::env::set_var("SSL_CERT_DIR", dir);
        }
    }
}

#[cfg(not(target_os = "linux"))]
fn pin_system_ca_paths() {}

pub fn run() {
    pin_system_ca_paths(); // первой строкой, пока других потоков ещё нет
    tauri::Builder::default()
        // ...
}

Функцию важно вызвать первой строкой в run(). В этот момент асинхронная среда ещё не запущена, других потоков нет, и менять окружение безопасно. Переменные, которые пользователь задал сам, функция не трогает.

После этого на ALT p11 HTTPS работает и до, и после проверки обновлений. В МСВСфере пропадает ошибка чтения, а на Debian и Ubuntu ничего не меняется, openssl-probe находит там те же пути, что зашиты в плагин.

Как стоило бы исправить в самом плагине

Если плагину нужны пути к сертификатам для своего HTTP-клиента, их лучше передавать клиенту напрямую, а не через окружение процесса. А если без переменных никак, то искать реальные пути через openssl-probe вместо путей из Debian. Я завёл об этом задачу в репозитории tauri-apps/plugins-workspace.

Что я для себя вынес

Библиотека, которая меняет переменные окружения, меняет поведение всех остальных библиотек в процессе. Если видите set_var в зависимости, проверьте, кто ещё читает эти переменные.

Пути к сертификатам в Linux не стандартизованы. TLS стоит проверять хотя бы на трёх семействах, Debian, RHEL и ALT.

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

Проверял на ALT Linux p11, МСВСфере 10.1 и Debian 12 в контейнерах и в самом S3 TM Browser, где эту ошибку и нашёл. Если встречали похожее в других фреймворках, расскажите в комментариях.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.