Как OpenVPN разрешает сертификат и ключ: встроенный PEM или путь
Одна и та же директива несёт либо путь к файлу, либо встроенный объект PEM. Прочитайте, какую из форм использует загруженный профиль, прежде чем перемещать сертификаты.
Сообщение говорит, что OpenVPN не может загрузить встроенный сертификат, а имя файла, которое вы держите в голове, утверждает обратное. То, что получил парсер, важнее того, что содержит файловая система, потому что директива cert принимает два совершенно разных типа значения. Один — это путь, который она открывает; другой — текст PEM, пришедший внутри конфигурации. Пока вы не знаете, какую форму получил работающий клиент, любое исправление — догадка.
Две формы хранения, одна директива
Директивы cert, key и ca принимают путь или встроенный блок. Когда значение — путь, OpenVPN открывает этот файл и читает объект с диска. Когда конфигурация содержит блок, ограниченный тегами cert, та же директива вместо этого использует содержимое между этими тегами, и отдельный файл не участвует.
Обе формы в итоге дают один и тот же тип объекта, поэтому работающая установка может переходить между ними без каких-либо других изменений. Профиль, несущий встроенный клиентский сертификат и закрытый ключ, самодостаточен, и его копирование на другую машину работает без копирования каталога сертификатов.
Обратное неверно. Положить client.crt рядом с профилем, который встраивает собственные данные, ничего не меняет, потому что ничто в конфигурации не ссылается на этот файл. Это самая частая причина, по которой файл, явно существующий, выглядит игнорируемым.
Как встроенный блок становится объектом
Встроенное содержимое извлекается из конфигурации и передаётся парсеру PEM как текст. Этот парсер ожидает обычные границы: строку BEGIN, полезную нагрузку base64 и соответствующую строку END. Если блок был обрезан неудачным копированием, обёрнут в HTML страницей загрузки или открыт в редакторе, нормализовавшем дефисы, парсер терпит неудачу на содержимом, которое выглядит присутствующим.
<cert>
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</cert>
Поэтому и формулировка ошибки вводит в заблуждение. Слово встроенный описывает, куда посмотрел загрузчик, а не что он там нашёл. Файл, названный в конфигурации, и встроенный в неё объект могут дать одну и ту же жалобу, когда лежащие в основе байты непригодны.
Проверьте, какую форму загруженный профиль действительно использует, а не какой файл случайно открыл ваш редактор:
grep -nE '^(ca|cert|key) |^<(ca|cert|key)>' client.ovpn
Как разрешается ссылка на путь
Значение пути открывается относительно рабочего каталога клиента, а не каталога, содержащего конфигурацию. Под systemd этот рабочий каталог — то, что задаёт юнит, а интерактивный запуск из вашей оболочки наследует нечто совсем другое. Поэтому один и тот же файл конфигурации может работать при ручной проверке и отказывать как служба.
Относительные пути наследуют ту же неоднозначность. Увидев голое имя файла, спросите, каков текущий каталог процесса в продакшене, вместо того чтобы предполагать совпадение с вашим терминалом.
Для сертификата, хранящегося отдельно, автономный разбор подтверждает объект, прежде чем вы будете отлаживать что-либо выше него:
openssl x509 -in client.crt -noout -subject -issuer -dates
openssl pkey -in client.key -noout -check
Эти команды доказывают, что файлы читаемы и структурно корректны. Они не доказывают, что партнёр доверяет издателю, что сертификат всё ещё в пределах срока действия или что ключ принадлежит ему.
Что ошибка парсера может и чего не может сказать
Сбой разбора локализует проблему на одном входе и одном этапе. Он не говорит, какую из двух форм хранения использовал клиент, и не говорит, был бы объект принят. Это отдельные вопросы с отдельными доказательствами.
Когда разбор успешен, следующий сбой относится к более позднему этапу. Отказ в доверии, несовпадение назначения или несовпадение ключа сообщает партнёр или уровень TLS, а не загрузчик. Повторная замена одного и того же файла не сдвинет эту ошибку.
Держите чувствительную половину вне публичных отчётов, пока ведёте расследование. Делитесь именами директив, текстом ошибки и структурой блока, удалив материал ключа и любую идентифицирующую нагрузку. Руководство openvpn(8) описывает обе формы файловых директив и поведение рабочего каталога, стоящее за ними.