Читать 7 мин

Может ли канонизация имени SSH изменить одобренную цель?

Разбираем, как канонизация имени SSH меняет цель, повторно читает конфигурацию и влияет на данные, которые нужно показать при одобрении.

Может ли канонизация имени SSH изменить одобренную цель?

Одобрение с текстом ssh build не обязательно разрешает подключение к машине, которая в итоге примет TCP-соединение. OpenSSH может дополнить короткое имя, пройти по разрешённой записи CNAME, повторно разобрать конфигурацию, выбрать другого пользователя или порт, а затем разрешить полученное имя в один из нескольких адресов. Если шлюз одобряет только текст от агента, в одобрении может быть указана одна цель, а SSH-клиент использует другую.

Это не довод против канонизации. Короткие имена удобны, канонические упрощают конфигурацию, а ключи хоста по-прежнему подтверждают личность сервера. Границу одобрения нужно поставить после того, как OpenSSH вычислил фактическое назначение, но до открытия сокета. Человек должен видеть запрошенное имя, окончательное имя и адрес, а также все существенные изменения из конфигурации.

В одной команде SSH скрыты четыре идентификатора

Цель SSH нельзя свести к одной строке. Именно такое упрощение создаёт описанную здесь ошибку. Надёжная система одобрения разделяет четыре идентификатора:

  • Запрошенное имя приходит в аргументе от агента, например build.
  • Фактическое имя хоста OpenSSH получает после подстановки Hostname и канонизации, например build.ops.example.
  • Адрес узла выбирается при открытии сокета.
  • Подтверждённая личность задаётся ключом или сертификатом хоста, принятым для соединения.

Эти значения отвечают на разные вопросы. Запрошенное имя фиксирует намерение. Фактическое имя управляет последующим сопоставлением конфигурации и обычно поиском ключа хоста. Адрес показывает, куда ушли пакеты. Ключ подтверждает, какой сервер владеет закрытым ключом. До соединения для одобрения нужны первые три значения, а после обмена ключами в результат аудита нужно добавить четвёртое.

OpenSSH сам показывает это различие в ssh_config. Токен %n означает исходное имя удалённого хоста из командной строки, а %h означает имя после обработки конфигурации. %k содержит ещё одно значение: HostKeyAlias, если он задан, иначе исходное имя. Эти токены нужны потому, что OpenSSH не может безопасно считать имена на всех этапах одинаковыми.

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

Канонизация может переписать имя до соединения

CanonicalizeHostname определяет, будет ли OpenSSH явно переписывать имя. По умолчанию установлено no, поэтому поиск выполняет системный резолвер. При yes OpenSSH перебирает суффиксы из CanonicalDomains для прямых соединений. При always канонизация также применяется к целям, доступным через ProxyCommand или ProxyJump.

Допустим, агент запрашивает такое действие:

host: build
user: deploy
command: /usr/local/bin/release status

На клиенте может быть такая конфигурация:

Host *
    CanonicalizeHostname yes
    CanonicalDomains ops.example
    CanonicalizeFallbackLocal no
    CanonicalizeMaxDots 1
    CanonicalizePermittedCNAMEs *.ops.example:*.hosts.example

Сначала OpenSSH попробует build.ops.example. Если DNS вернёт разрешённую запись CNAME на build-07.hosts.example, это назначение может стать каноническим именем. В запросе всё ещё написано build, но клиент уже подключается под другим именем. Карточка, которая показывает только запрос, скрывает важное преобразование.

Ограничения требуют внимания. Значение CanonicalizeMaxDots по умолчанию равно 1, поэтому имя с одной точкой тоже может пройти канонизацию, а не только одиночная метка. Для CanonicalizeFallbackLocal по умолчанию задано yes; если настроенные канонические домены не дали результата, OpenSSH передаёт исходное имя системному резолверу с его правилами поиска. Значение no приводит к явной ошибке. Для шлюза это обычно лучше, поскольку локальный список поиска может меняться вместе с сетью и временем.

Переход по CNAME ограничивается отдельно. По умолчанию CanonicalizePermittedCNAMEs имеет значение none, а правила связывают разрешённые шаблоны источника и назначения. Такой вариант разумен. Широкое правило *:* превращает DNS-псевдоним в неограниченный этап перезаписи. Узкое правило документирует ожидаемый переход между пространствами имён, например от сервисных имён в ops.example к хостам в hosts.example.

Руководство OpenSSH точно описывает эти параметры, но уровень одобрения не должен просто копировать их значения на карточку. Он обязан выполнить тот же расчёт с той же конфигурацией клиента. Самостоятельная реализация поиска суффиксов и правил CNAME в сервисе одобрения со временем расходится с SSH-клиентом из-за новых версий и локальных изменений.

Второй разбор меняет не только DNS

Когда канонизация включена, OpenSSH повторно обрабатывает конфигурацию с новым целевым именем. Второй проход может активировать блоки Host и Match, которые не совпадали с исходным псевдонимом. Поэтому предварительная проверка одного DNS пропускает другие изменения назначения.

Предположим, каноническое имя совпало с таким блоком:

Match canonical host *.prod.ops.example
    User release
    Port 2222
    IdentityFile ~/.ssh/prod_release
    ProxyJump bastion.ops.example

Запрос к deploy может сначала превратиться в deploy.prod.ops.example, а затем получить другого удалённого пользователя, порт, файл идентификации и промежуточный хост. Для большинства директив OpenSSH использует первое найденное значение. Поэтому результат зависит от предыдущих блоков и их порядка. По одному совпавшему блоку окончательную конфигурацию определить нельзя.

Условие canonical совпадает только во время повторного разбора после канонизации. Условие final требует последнего прохода, даже если канонизация выключена. Когда она включена, canonical и final совпадают в одном проходе. Критерий host проверяет цель после подстановки Hostname или канонизации, а originalhost проверяет значение из командной строки. Из-за этих деталей простая на вид конфигурация сильно зависит от состояния.

Опасная схема выглядит так: система одобряет host=deploy, user=agent, port=22, затем передаёт ssh deploy обычному клиенту и считает, что поля не изменятся. Если клиент всё ещё может менять User, Port, ProxyJump, RemoteCommand, перенаправления или выбор идентификации, одобрено предложение, а не выполненное действие.

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

Происхождение конфигурации входит в понятие цели

Окончательное назначение зависит от файлов, параметров командной строки, окружения и локальной сети, которые оценил OpenSSH. Если не записать эти входные данные, одобренный смысл легко изменить. Руководство задаёт обычный порядок: параметры командной строки, пользовательский файл, системный файл. Для большинства директив действует первое найденное значение.

Агент, способный добавлять произвольные параметры SSH, может обойти осторожные значения по умолчанию. -F выбирает другой файл, а -o позволяет напрямую задать Hostname, ProxyCommand, ProxyJump, Port, User, HostKeyAlias или перенаправления. Шлюз должен разбирать действие SSH в типизированные поля и отклонять неподдерживаемые параметры, а не принимать непрозрачную командную строку. Если без исходных параметров не обойтись, нужно классифицировать каждый влияющий на назначение параметр и включить его нормализованное значение в одобрение.

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

Match exec не просто сравнивает строку условия: OpenSSH запускает указанную команду в оболочке пользователя и считает нулевой код возврата совпадением. Match localnetwork меняет конфигурацию по адресам активных локальных интерфейсов. Само руководство предупреждает, что локально наблюдаемая сеть ненадёжна для чувствительной конфигурации, когда её задаёт DHCP. Один запрос ssh build может получить другую цель после подключения VPN, изменения результата скрипта или появления подключённого файла.

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

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

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

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

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

Ответ DNS может измениться после показа карточки

Блокируйте хранилище и действия
Пока хранилище заблокировано, Sallyport отклоняет все действия до применения ключа.

Даже при неизменном фактическом имени его адрес может поменяться между одобрением и соединением. Ротация DNS, разделённые представления, смена VPN, поисковые пути и обычные обновления записей дают другой ответ. Классический разрыв между проверкой и использованием возникает, если сервис разрешает имя и показывает адрес, а процесс SSH позже разрешает его снова.

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

Несколько записей A или AAAA требуют явного правила. Можно показать ограниченный набор и позволить клиенту попробовать его элементы, но карточка должна сообщать об этом, а аудит должен записать сработавший адрес. Если набор изменился, старое одобрение не распространяется на новый адрес. Для особо важных хостов проще одобрять один выбранный адрес.

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

Правильный ключ хоста не подтверждает запрошенное имя

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

Архитектура SSH в RFC 4251 описывает локальную базу, которая связывает введённое пользователем имя с ключом хоста. OpenSSH добавляет к этой модели имена из конфигурации. HostKeyAlias требует использовать псевдоним вместо настоящего имени при чтении и сохранении ключей, а также при проверке сертификатов. Это полезно для туннелей и нескольких серверов на одном адресе, но создаёт ещё одно имя для аудита.

RFC 4255, определяющий SSHFP, особенно уместен. Он предупреждает о неполных именах и внедрённых путях поиска DNS, а в такой ситуации рекомендует сначала проверить локальную базу ключей и лишь затем отпечаток из DNS. Он также запрещает доверять записи SSHFP без аутентификации DNSSEC. Совет касается личности сервера, но подтверждает общий вывод: расширение имени меняет контекст безопасности, а ответ резолвера сам по себе не доказывает личность.

RFC 4462 формулирует ещё более жёсткое предупреждение для GSS-API. Нельзя строить целевое имя сервера по незащищённому DNS, потому что злоумышленник может изменить соответствие псевдонима или адреса и выдать себя за сервер. Даже при обычной аутентификации открытым ключом вывод не меняется. Незащищённая перезапись имени не должна молча решать, какую личность якобы одобрил защитный механизм.

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

Предварительная проверка должна использовать те же входные данные

Отзывайте активный процесс агента
Журнал Sessions позволяет отозвать разрешение у работающего процесса агента.

Полезная проверка воспроизводит окончательную конфигурацию клиента и наблюдает маршрут, не выполняя удалённую команду. ssh -G печатает конфигурацию после оценки блоков Host и Match. ssh -vvv добавляет диагностику разрешения, соединения и ключа; руководство называет три флага -v максимальным уровнем подробности.

Запустите эту последовательность в контролируемом окружении и замените build точным токеном из запроса:

ssh -G build | awk '
  $1 == "hostname" || $1 == "user" || $1 == "port" ||
  $1 == "proxyjump" || $1 == "proxycommand" ||
  $1 == "hostkeyalias" || $1 == "canonicalizehostname" {
    print
  }
'
ssh -vvv -o BatchMode=yes -o SessionType=none \
  -o ConnectTimeout=5 build 2>&1

Первая команда даёт вывод такой формы:

user deploy
hostname build-07.hosts.example
port 22
canonicalizehostname true
proxyjump none

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

У диагностики есть две ловушки. Во-первых, ssh -G показывает вычисленную конфигурацию, но не доказывает, какой адрес выберет и достигнет следующее соединение. Во-вторых, подробная проба может открыть сеть и обменяться ключами даже с SessionType=none; запускайте её только после отдельного разрешения. BatchMode=yes убирает интерактивные запросы, но не превращает соединение в локальное вычисление.

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

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

Запись одобрения должна показывать преобразование

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

Практическая запись может выглядеть так:

{
  "requested": {
    "host": "build",
    "user": "deploy",
    "command": "/usr/local/bin/release status"
  },
  "effective": {
    "host": "build-07.hosts.example",
    "address": "192.0.2.44",
    "port": 22,
    "user": "deploy",
    "proxy": null,
    "host_key_alias": null
  },
  "resolution": {
    "canonicalized": true,
    "source": "build.ops.example",
    "permitted_cname": true
  },
  "binding": "sha256:REDACTED"
}

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

Не скрывайте поле на карточке только потому, что оно включено в привязку. Люди разрешают то, что видят. Покажите build -> build-07.hosts.example (192.0.2.44) как одно понятное преобразование, затем пользователя, порт, прокси и команду. Отпечатки и происхождение можно убрать в раскрываемые детали, кроме случая с новой или изменившейся личностью хоста.

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

Прокси и повторное использование требуют отдельных привязок

Одобряйте процесс за командой SSH
Первая карточка Sallyport определяет новый процесс по центру подписи кода.

С прокси канонизация ведёт себя иначе. CanonicalizeHostname yes действует только на соединения без ProxyCommand и ProxyJump; always включает соединения через прокси. Одно слово может изменить окончательное имя и запустить второй разбор. Система должна записывать фактическое значение, а не считать канонизацию глобально включённой или выключенной.

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

Мультиплексирование создаёт менее заметный обход. Если уже существует подходящая сессия ControlMaster, новый вызов может использовать её вместо ожидаемого соединения. Главная сессия могла раньше разрешить DNS, применить старую конфигурацию и проверить ключ до текущего одобрения. Для контролируемых действий отключите повторное использование или свяжите решение с неизменяемой личностью главной сессии и проверьте её перед открытием канала.

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

Аудит должен сохранить намерение и результат

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

Sallyport проводит действия SSH через встроенный помощник sp-ssh, а ключи остаются в зашифрованном хранилище, поэтому агент не получает закрытый ключ. Журналы Activity и Sessions строятся из одного зашифрованного аудита с хеш-цепочкой, поэтому путь одобрения и исполнения может хранить запрошенную и наблюдаемую цель, а не одну строку, похожую на команду.

Одного хранения мало. Определите проверяемые инварианты: использованный адрес входил в одобренный набор, имя и порт совпали с билетом, принятая личность записана, каждая повторная попытка и переход разрешены. Подайте в тест конфигурацию с CanonicalizeHostname, разрешённым CNAME, блоком Match canonical и двумя ответами DNS. Измените один факт между проверкой и соединением и убедитесь, что исполнение остановилось.

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

Вопросы и ответы

Что делает канонизация имени хоста SSH?

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

Включён ли CanonicalizeHostname по умолчанию?

Нет. OpenSSH задаёт CanonicalizeHostname no по умолчанию, поэтому явная перезапись выключена без настройки. При обычном поиске системный резолвер всё равно может применить собственные правила.

Чем отличаются %n и %h в ssh_config?

%n содержит исходный токен хоста из командной строки. %h содержит удалённый хост после Hostname и канонизации, поэтому в одобрении и аудите их нельзя считать одним значением.

Может ли канонизация SSH пройти по CNAME?

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

Показывает ли ssh -G окончательный IP-адрес?

Нет. ssh -G показывает вычисленную конфигурацию, включая hostname, но не доказывает, какой адрес достигнет следующее соединение. Разрешение и привязку должен выполнять исполнитель, открывающий сокет.

Делает ли правильный ключ хоста канонизацию безопасной?

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

Что показывать в одобрении SSH, имя или IP-адрес?

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

Как одобрять несколько адресов DNS?

Одобрите один выбранный адрес или покажите ограниченный набор, который может пробовать коннектор. Запишите использованный адрес и отклоните любой адрес вне набора.

Меняет ли ProxyJump канонизацию имени?

Может менять. CanonicalizeHostname yes пропускает цели через прокси, а always включает их; каждый промежуточный хост добавляет отдельную личность для привязки.

Может ли существующий ControlMaster обойти проверку цели?

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

Sallyport

Sallyport выполняет API-вызовы и SSH-команды за вашего ИИ-агента. Ключи остаются в локальном хранилище на вашем Mac; вы подтверждаете каждый запуск, и каждое действие попадает в запечатанный журнал.

© 2026 Sallyport · Открытый код по лицензии Apache-2.0 · Oleg Sotnikov