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

Файл никогда не был той уязвимостью, о которой вы думали

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

Более тихие риски находятся ниже по потоку: ключ, напечатанный при отладке, исключение, сериализующее весь объект результата, обработчик сбоя, пишущий дамп памяти, агрегатор журналов, получающий путь к файлу, а затем содержимое. Отказ от временного файла не делает ничего ни для одного из них, и вера в обратное — это путь, которым доброжелательная переработка оставляет секрет более открытым, чем раньше. Рассматривайте изменение как одно улучшение в цепочке, а не как решение.

Что клиент пишет, когда имя файла не задано

Текущие версии клиента отправляют сгенерированный ключ на стандартный вывод, когда команда ключа задана без пути вывода. Именно это документированное поведение делает подход в памяти жизнеспособным и устраняет необходимость старого трюка с указанием инструменту пути к устройству.

import subprocess
proc = subprocess.run(
    ["openvpn", "--genkey", "tls-crypt"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    check=True,
)
key_material = proc.stdout

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

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

Два потока, два значения

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

Держите отдельным и путь отказа. Когда вызов не удался, сообщите команду, которая не сработала, и статус выхода, а затем сознательно решите, безопасно ли показывать какую-либо часть стандартной ошибки. Сбрасывать весь объект результата в журнал — обычное сокращение, и оно может унести ключ с собой.

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

Если вы всё же его храните, делайте это осознанно

Большинству развёртываний в конце концов нужен ключ на диске, на шлюзе или в хранилище секретов. Запишите его один раз, с ограничительными правами с момента создания, а не поздним chmod, и сделайте запись атомарной, чтобы частичный файл никогда не стал тем, который служба читает при запуске. Переименования из пути в том же каталоге для этого обычно достаточно. Удалите подготовительный путь в ветке отказа, потому что неудачная запись — это ровно та копия, которую люди забывают.

Задайте режим файла при создании и держите режим каталога тоже ограничительным. Избегайте любого пути под местом, которое задача резервного копирования сметает не задумываясь, потому что секрет в ночной резервной копии — это секрет в каждой копии этой резервной копии. Те же рассуждения применимы к общему тому, который могут читать другие службы.

Общий ключ — это не идентичность

Генерация этого материала не выдаёт сертификат, не регистрирует клиента и не устанавливает, кто подключается. Общий ключ защищает канал между сторонами, которые уже доверяют друг другу; идентичность по-прежнему приходит из сертификата и любой аутентификации перед ним.

Типы ключей тоже не взаимозаменяемы. Симметричный ключ для одной функции не является готовой заменой для другой, и новые выпуски добавляют свои варианты с разными файлами на каждом конце. Генерируйте тот тип, который ваша конфигурация действительно называет, а затем используйте его в роли, которую этот тип определяет.

Проверяйте результат настоящим подключением, а не просмотром размера файла. Если рукопожатие завершается и журналы не показывают предупреждений о ключе, путь генерации верен; если бы ключ был обрезан объединённым потоком, соединение отказало бы так, что указывало бы на вторую сторону, а не на скрипт.

Похожие статьи