Сторонний источник пакетов добавляется на сервер один раз и живёт годами, а его ключ всё это время лежит в общем хранилище, куда его положила команда apt-key. Команда объявлена устаревшей, а в свежих выпусках дистрибутивов удалена целиком, поэтому обновление привычных источников сначала сыплет предупреждениями, а затем останавливается совсем. Лечится всё переносом ключей: извлечением из старой связки, конвертацией текстового формата в бинарный и раскладкой по отдельным файлам с привязкой каждого ключа к своему источнику. На сервере с десятком репозиториев вся работа занимает около получаса. Статья разбирает причины ухода команды, поломки, которые она влечёт, инвентаризацию ключей, извлечение и конвертацию, два варианта размещения файлов, права доступа, проверку обновления и порядок миграции с чисткой старых следов.

Причины устаревания apt-key и замысел новой схемы доверия

Команда apt-key долгое время была единой точкой управления ключами пакетных источников: она импортировала ключи, показывала их список и удаляла отжившие. Все импортированные ключи оседали в общем файле /etc/apt/trusted.gpg либо в каталоге /etc/apt/trusted.gpg.d, и каждое такое попадание означало одно и то же: ключ признаётся доверенным для любого источника на машине. Эта модель и оказалась слабым местом всей конструкции. Ключ от небольшого стороннего проекта, добавленный однажды по инструкции из его документации, получал право подписывать пакеты для всей системы, включая официальные источники дистрибутива. Срок действия ключей при этом никто не контролировал: типичный ключ добавлялся без даты окончания и жил в связке вечно, передаваясь по наследству от клона к клону вместе с образом сервера.

Вторая причина носит технический характер. Внутри apt-key оборачивала вызовы gpg, подстраиваясь под меняющиеся версии криптографического набора gnupg, и каждая смена его внутреннего устройства грозила сломать совместимость. Содержать прослойку стало дороже, чем перенести доверие на уровень конфигурации источников. Так сложилась нынешняя схема: каждый источник сам называет файл своего ключа, а общее хранилище остаётся достоянием истории. Рабочий набор прошлых лет выглядел знакомо каждому администратору:

apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ABCDEF1234567890
apt-key add example-archive-keyring.asc
apt-key list

Первая строка забирала ключ с сервера ключей, вторая добавляла его из текстового файла, третья печатала связку с отпечатками и сроками. На системах, где apt-key ещё присутствует, список по-прежнему открывается; на свежих выпусках остаются только прямые вызовы gpg.

Последствия удаления apt-key для обновления сторонних источников

Поломка проявляется не сразу и без драмы. Пока источники обновляются, ничто не намекает на проблему: установленное программное обеспечение работает, списки пакетов приходят штатно. Первый симптом, предупреждения с пометкой W о том, что ключ хранится в устаревшей общей связке trusted.gpg и его желательно перенести в отдельный файл. Затем, когда дистрибутив доезжает до сборки без apt-key, предупреждения сменяются отказом проверки подписи: apt сообщает об отсутствии публичного ключа с кодом NO_PUBKEY и помечает источник как неподписанный. Обновление списка пакетов из такого источника не выполняется, установка его пакетов блокируется до переноса ключа. Собрать все жалобы apt в одном месте помогает фильтрация вывода обновления:

apt-get update 2>&1 | grep -iE 'NO_PUBKEY|trusted.gpg|not signed'

Пустой вывод означает, что все источники проверяются штатно. Строка с NO_PUBKEY называет источник и отпечаток недостающего ключа, и с неё начинается миграция. Ничего при этом не пропало: ключи лежат на диске, кэш пакетов цел, сломан только механизм проверки доверия, и чинится он переносом файлов, без переустановок и без пересборки системы.

Инвентаризация сторонних источников и их ключей перед переносом

Миграция начинается с ревизии, а не с копирования файлов. Задача ревизии проста: составить таблицу соответствия источник и ключ, чтобы после переноса не осталось ни одного потерянного файла. Источники перечислены в файле /etc/apt/sources.list и в каталоге /etc/apt/sources.list.d, где соседствуют старый построчный формат с расширением .list и новый формат с расширением .sources. Собрать все записи одной командой удобнее всего:

grep -rE '^(deb |URIs:|Suites:)' /etc/apt/sources.list /etc/apt/sources.list.d/

Источники, у которых уже прописан параметр signed-by, трогать не нужно: их перевели на адресную схему заранее. Дальше осматривается старое хранилище. Файл /etc/apt/trusted.gpg существует не на каждой машине: на чистой системе его нет, а ключи лежат файлами в каталоге trusted.gpg.d. Содержимое связок печатается прямым вызовом gpg без всяких прослоек:

gpg --no-default-keyring --keyring /etc/apt/trusted.gpg --list-keys --with-colons
gpg --no-default-keyring --keyring /etc/apt/trusted.gpg.d/example.gpg --list-keys

В выводе интересуют отпечаток, идентификатор и срок действия каждого ключа. Сопоставление с источниками идёт по строке владельца: она почти всегда содержит название проекта, и по нему ключ привязывается к своей строке deb. Полезно завести короткую опись: какой источник, каким ключом подписан, куда переносим. Системы, где apt-key ещё работает, дают тот же список командой apt-key list, но привычка к прямому gpg окупается быстро, потому что переживает любое обновление дистрибутива.

Извлечение ключей из старого хранилища и конвертация формата

Ключи в старой связке лежат в бинарном виде, но путь миграции всё равно проходит через конвертацию, потому что новому ключу нужен одиночный файл, а не пачка. Если сохранился исходный текстовый файл в бронированном формате с блоком PUBLIC KEY, скачанный когда-то из документации проекта, задача упрощается: его достаточно превратить в бинарный вид командой gpg --dearmor. Если файла нет, ключ экспортируется прямо из старой связки по идентификатору, полученному на инвентаризации:

mkdir -p /etc/apt/keyrings
gpg --no-default-keyring --keyring /etc/apt/trusted.gpg \
    --armor --export ABCDEF1234567890 > /tmp/example-key.asc
gpg --dearmor --output /etc/apt/keyrings/example-archive-keyring.gpg /tmp/example-key.asc
rm /tmp/example-key.asc

Флаг брони в экспорте выдаёт знакомый многим текстовый файл, а вторая команда снимает бронь и кладёт на постоянное место бинарный файл. Промежуточная копия удаляется, чтобы на диске не плодились разрозненные версии одного ключа. Содержимое нового файла проверяется тем же способом, что и старая связка:

gpg --no-default-keyring --keyring /etc/apt/keyrings/example-archive-keyring.gpg --list-keys

Если в выводе виден отпечаток из описи, ключ переехал без потерь: экспорт и конвертация не меняют ни отпечаток, ни внутренние подписи ключа. Тот же приём раскладывает содержимое каталога trusted.gpg.d по отдельным связкам: из пачки вынимается ключ конкретного проекта, и каждый источник получает собственный файл вместо общей кучи.

Разница форматов стоит одного взгляда. Текстовый файл в броне несёт те же данные ключа, свёрнутые в печатные символы с шапкой и подписью блока, он переезжает в письма и мануалы без искажений. Бинарный файл компактнее, и именно его ожидает каталог доверенных ключей, поэтому конвертация сводится к перекладке одного и того же содержимого из текстовой упаковки в двоичную, без изменения криптографической сути. Каталог trusted.gpg.d рассчитан на бинарные файлы с расширением .gpg, и текстовый ключ, положенный туда как есть, работать не обязан.

Размещение в каталоге trusted.gpg.d и отдельные связки с signed-by

Короткий путь: бинарный файл ключа кладётся в каталог /etc/apt/trusted.gpg.d с расширением .gpg, и обновление сразу оживает, потому что apt читает всё содержимое каталога как доверенное. Такой перенос честно решает задачу восстановления работоспособности, но воспроизводит старую слабость: ключ снова становится доверенным для всех источников машины. Для домашнего компьютера и для аварийного восстановления вариант приемлем, для сервера, которому жить годами, лучше адресная схема.

Правильный путь привязывает ключ к конкретному источнику. Файл ключа живёт в отдельном каталоге, например /etc/apt/keyrings, а строка источника называет его через параметр signed-by. Построчный формат в общем виде выглядит так:

deb [arch=amd64 signed-by=/etc/apt/keyrings/example-archive-keyring.gpg] mirror.example.org/linux bookworm main

Формат с расширением .sources записывает тот же смысл полем Signed-By:

Types: deb
URIs: mirror.example.org/linux
Suites: stable
Components: main
Signed-By: /etc/apt/keyrings/example-archive-keyring.gpg

С параметром signed-by доверие становится адресным: ключ проверяет подписи только своего источника, и инцидент у мелкого проекта больше не открывает дверь во всю систему. Свежие версии apt принимают в этом параметре и текстовый файл в броне с расширением .asc, но бинарный .gpg надёжнее по совместимости со старыми сборками, поэтому конвертация остаётся общепринятой практикой. Отдельная оговорка касается ключей самого дистрибутива: их связки ставятся пакетами вида debian-archive-keyring и ubuntu-keyring и обновляются вместе с системой, вручную эти файлы не правятся и в миграции не участвуют.

Права на файлы ключей и проверка обновления списка пакетов

Файлами ключей распоряжается root, и читаться они должны им же: владелец root:root, права 0644 на файлы и 0755 на каталоги. Излишне тесные права помешают подсистеме apt прочитать ключ, излишне широкие откроют запись посторонним, а возможность записать чужой ключ в доверенную связку равнозначна подмене доверия всей машины. Приведение прав занимает три строки:

chown -R root:root /etc/apt/keyrings
chmod 0755 /etc/apt/keyrings
chmod 0644 /etc/apt/keyrings/*.gpg

Проверка после переноса идёт в два шага. Первый шаг, обновление списка пакетов: команда должна отработать без строк с NO_PUBKEY и без жалоб на устаревшую связку. Второй шаг, тестовая установка из перенесённого источника: берётся небольшой пакет, доступный только в этом источнике, и ставится целиком, с полной проверкой подписи. Происхождение и кандидат показываются политикой пакетов:

apt-get update
apt-get install --assume-yes example-tool
apt-cache policy example-tool

Если установка прошла, а в политике виден перенесённый источник, репозиторий закрыт. Источники при проверке перечитываются целиком, поэтому проблемный ключ проявляется сразу первой же жалобой apt, и пропустить её трудно.

Порядок миграции по одному источнику и чистка старых файлов

Работа без плана растягивает полчаса на вечер: ключ перепутан, источник временно отключён, а список того, что уже перенесено, живёт только в памяти. Выстроенная последовательность выглядит так:

  1. Составить опись всех сторонних источников и их ключей, отметив источники, уже работающие через signed-by;
  2. Перенести ключи первого источника, прописать параметр signed-by и проверить обновление списка и тестовую установку;
  3. Повторить перенос для остальных источников по одному, не смешивая правки нескольких репозиториев в одной серии команд;
  4. Удалить перенесённые ключи из старой связки по их идентификаторам;
  5. Убрать с диска промежуточные текстовые файлы и неактуальные записи в файлах источников;
  6. Запустить финальное обновление списка пакетов и убедиться в отсутствии предупреждений.

Удаление из старой связки выполняется прямым вызовом gpg, а там, где apt-key ещё жив, доступен и его вариант удаления:

gpg --no-default-keyring --keyring /etc/apt/trusted.gpg --delete-keys ABCDEF1234567890

Файл trusted.gpg после удаления всех посторонних ключей можно оставить пустым или убрать вовсе, если связка создана вручную и не содержит системного содержимого. Каталог trusted.gpg.d не трогается: в нём живут связки дистрибутива, поставленные пакетами. Завершающий штрих, опись: перечень файлов ключей и их привязка к источникам попадает в систему управления конфигурациями, и новый сервер получает актуальную схему доверия при развёртывании, без ручных импортов и без наследства из образов. Схема со signed-by после этого работает годами без вмешательства: ротация ключа сводится к замене одного файла, и память о команде apt-key остаётся только в старых инструкциях.