# Раздельные SSH-credentials для безопасного доступа к production

Один SSH-credential, который даёт доступ к development, staging и production, превращает небольшое удобство в широкие удалённые полномочия. Из-за него согласования тоже становятся формальностью: человек может одобрить безобидную задачу в staging, хотя тот же credential через несколько минут откроет сессию в production.

Разделяйте SSH-credentials по классам систем и по уровню риска. Затем отразите это разделение в конфигурации клиента, закрепите его на серверах и проверяйте запрещённые пути с такой же серьёзностью, как разрешённые. Я видел, как команды называли это контролем доступа, хотя всего лишь давали одному ключу три имени в `~/.ssh/config`. Это не граница.

## Один credential создаёт одну область отказа

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

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

Поэтому один credential с разными host alias ничего не решает. Такие записи выглядят аккуратно:

```sshconfig
Host dev-db
  HostName dev-db.internal

Host prod-db
  HostName prod-db.internal
```

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

Раздельные credentials меняют результат. Украденный credential development должен завершаться ошибкой при обращении к staging и production. Процесс, которому разрешено использовать staging-credential, не должен иметь закрытого материала или возможности подписи, позволяющих пройти аутентификацию в production. Это разные меры контроля. Первая ограничивает последствия взлома. Вторая не даёт законному, но слишком широкому процессу выполнить неправильное действие.

Не путайте это с ротацией паролей. Ротация со временем заменяет credential. Разделение определяет, где credential вообще может работать. Нужны обе меры, но ротация не исправит credential, который намеренно принимают повсюду.

## Имен сред недостаточно для границы

Development, staging и production полезны как обозначения только тогда, когда за ними стоят разные решения об уровне доверия. Хост с именем `staging` может содержать данные, похожие на production, отправлять настоящие письма, хранить секрет подписи или подключаться к рабочему платёжному endpoint. И наоборот, production-хост для мониторинга может требовать меньше полномочий, чем администратор базы данных в staging.

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

- Может ли этот аккаунт читать данные клиентов или регулируемые данные?
- Может ли он изменить работающий сервис, развёртывание, firewall или запись DNS?
- Может ли он получить другой credential или выдать себя за другой сервис?
- Может ли он перейти к системам с большими полномочиями?

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

Отдельный Unix-аккаунт тоже часто нужен. Аккаунт `deploy` может владеть каталогом выпуска и принимать ограниченную команду. `ops-read` может просматривать журналы, не изменяя service units. `admin` может выполнять обслуживание по более строгому правилу согласования. Отдельные аккаунты дают серверу место для назначения прав, а журналам - понятного субъекта.

Не позволяйте разным именам аккаунтов создать ложное чувство безопасности. Если один открытый ключ указан для `dev`, `deploy` и `admin` на разных машинах, закрытый ключ всё равно остаётся credential между средами. Отдельные аккаунты и отдельные credentials решают разные части задачи.

## Дайте каждому человеку и процессу собственную identity

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

Для человека создайте отдельные закрытые ключи для разрешённых областей. В именах указывайте scope, а не используйте расплывчатые варианты вроде `id_ed25519_new` или `server-key-final`.

```sh
mkdir -p ~/.ssh/identities
chmod 700 ~/.ssh/identities

ssh-keygen -t ed25519 \
  -f ~/.ssh/identities/id_ed25519_dev_alex \
  -C dev-alex

ssh-keygen -t ed25519 \
  -f ~/.ssh/identities/id_ed25519_stage_alex \
  -C stage-alex

ssh-keygen -t ed25519 \
  -f ~/.ssh/identities/id_ed25519_prod_alex \
  -C prod-alex
```

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

К автоматизации применяйте ту же дисциплину. Задача выпуска должна использовать credential, выданный этой задаче и этой среде, а не production-identity инженера, скопированную в хранилище секретов. Если доступ нужен нескольким задачам, выдайте им разные identities, если только у них не совпадают владелец, набор целей и полномочия команд. После инцидента credential должен позволять быстро ответить на вопрос: какой процесс им воспользовался?

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

## Конфигурация клиента должна предотвращать утечку identity

OpenSSH использует identities из конфигурации и, если это не ограничить, identities, предложенные SSH-agent. Это удобно, пока в agent не оказывается production-identity и подключение, предназначенное для staging, не проходит с её помощью.

В руководстве OpenSSH для `ssh_config` параметр `IdentitiesOnly` описан как ограничение, при котором для аутентификации открытым ключом используются только настроенные identity-файлы и сертификаты, даже если agent содержит другие identities. Установите его для каждого scoped alias. Для каждого alias укажите один явный identity-файл и не полагайтесь на порядок, в котором agent предлагает ключи.

```sshconfig
Host dev-*
  User devops
  IdentityFile ~/.ssh/identities/id_ed25519_dev_alex
  IdentitiesOnly yes
  ForwardAgent no

Host stage-*
  User release
  IdentityFile ~/.ssh/identities/id_ed25519_stage_alex
  IdentitiesOnly yes
  ForwardAgent no

Host prod-*
  User admin
  IdentityFile ~/.ssh/identities/id_ed25519_prod_alex
  IdentitiesOnly yes
  ForwardAgent no
```

Используйте alias, по которому среду легко узнать в истории терминала. Например, `prod-api-01` лучше, чем `api-01`, если и development, и production содержат API-хосты с похожими именами. Не прячьте цель за общим alias вроде `server`.

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

```sh
ssh -G prod-api-01 | grep -E '^(hostname|user|identityfile|identitiesonly|forwardagent) '
```

Вывод должен выглядеть примерно так:

```text
hostname prod-api-01.internal
user admin
identitiesonly yes
forwardagent no
identityfile ~/.ssh/identities/id_ed25519_prod_alex
```

Путь на вашей машине может раскрываться иначе. Важно, чтобы production-alias разрешался только в production-identity, а `identitiesonly` имел значение `yes`.

Частая проблема возникает после запуска `ssh-add` для удобства. В agent оказывается несколько identities. Без `IdentitiesOnly yes` клиент перебирает их на хосте, пока одна не сработает. Серверы часто ограничивают число попыток аутентификации, поэтому появляются непонятные ошибки. Хуже того, широкий credential может незаметно обеспечить успешное подключение, и оператор не поймёт, что использовал неправильный scope.

## Авторизация на сервере должна повторять разделение

Раздельные закрытые ключи работают только тогда, когда каждый сервер принимает соответствующий открытый ключ и отвергает остальные. Помещайте development-ключ на development-хосты, staging-ключ на staging-хосты, а production-ключ только туда, где нужен доступ к production.

Правило очевидно, но плохой шаблон встречается постоянно при срочной настройке: кто-то копирует целиком файл `authorized_keys` на новый хост. В нём часто хранятся старые identities, ключи бывших подрядчиков, ключи развёртывания и общий ключ администратора. Новый production-хост наследует решения о доступе, которые никто не пересмотрел.

Составляйте список авторизации аккаунта исходя из его задачи. Для deploy-аккаунта используйте ограничения OpenSSH, подходящие для неинтерактивной передачи или операции развёртывания. В руководстве OpenSSH для `authorized_keys` описан параметр `restrict`: он отключает перенаправление портов, пересылку agent, X11 forwarding и выделение PTY, если другой параметр явно их не разрешает. Ограниченная запись для развёртывания может выглядеть так:

```text
restrict,from="198.51.100.0/24" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... prod-release-job
```

Замените пример сети диапазоном, который действительно контролируете. Не добавляйте `from=` только потому, что он выглядит строго. Если задача запускается с меняющихся адресов или из пула hosted runner, неточное ограничение источника приведёт к сбою и заставит кого-то под давлением убрать все ограничения.

Используйте принудительные команды только для узкой автоматизации. Такая команда может не дать credential развёртывания получить shell, но превращается в обязательство по обслуживанию. Она должна проверять входные данные, безопасно выбирать пути и записывать запрос в журнал. Не применяйте её к аккаунту администратора, полагая, что теперь аккаунт безопасен: людям рано или поздно понадобится shell, и они начнут обходить ограничение.

Для интерактивного администрирования production обычно понятнее отдельный аккаунт с отдельным открытым ключом, чем сложный набор параметров `authorized_keys`. Ограничивайте аккаунт обычными правами хоста, записывайте действия sudo, если это применимо, и удаляйте открытый ключ, когда доступ больше не нужен.

## SSH-сертификаты полезны только при узких claims

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

Но сертификаты не устраняют необходимость в разных центрах доверия. Сертификат с production-principal не должен одновременно давать доступ к development и staging только потому, что один оператор работает со всеми тремя средами. Выпускайте разные сертификаты, используйте разные principals или отдельные центры сертификации, если этого требуют границы администрирования и риска.

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

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

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

## Пересылка agent пересекает невидимую границу

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

Поэтому пересылка особенно опасна для production-сессии, которая сначала попадает на jump host. Если jump host скомпрометирован или от имени этого аккаунта работает недоверенный процесс, он может запрашивать подписи у каждой identity, предложенной вашим agent. Так появляется боковой доступ, которого не было в исходном плане подключения.

Оставьте в конфигурации клиента такую настройку по умолчанию:

```sshconfig
Host *
  ForwardAgent no
```

Затем добавляйте только одно явное исключение, если его требует поддерживаемый процесс. Перед этим проверьте, нельзя ли использовать ProxyJump, отдельный credential bastion или выполнить операцию локально. ProxyJump передаёт SSH-соединение через промежуточный хост, но не открывает этому хосту ваш локальный agent тем же способом.

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

## Scope согласования должен совпадать со scope credential

Согласование, разрешающее новому процессу использовать SSH-credential, должно указывать среду credential и процесс, который его запросил. Запрос с текстом «запрошена команда ssh» заставляет человека делать слишком много выводов по слишком малому объёму информации.

Здесь команды часто смешивают два разных контроля. Scope credential отвечает на вопрос, где identity может аутентифицироваться. Scope согласования отвечает на вопрос, какой работающий процесс может использовать эту identity. Нужны оба. Отдельные production-credentials не дают процессу development случайно попасть в production, а согласование процесса не позволяет неизвестному локальному процессу воспользоваться доступным production-credential.

Согласование каждой команды кажется безопаснее, и после неприятного инцидента его часто предлагают. В обычном обслуживании это плохо работает: повторяющиеся запросы приучают операторов одобрять их, не читая. Подтверждение каждого использования оставляйте для credentials, само применение которых требует решения человека, например для важной identity администрирования production. Для обычной работы выдавайте авторизацию на сессию, которая заканчивается вместе с процессом, и сохраняйте узкий серверный scope credential.

Sallyport хранит SSH-credentials в зашифрованном хранилище и может требовать согласование для нового процесса агента или подтверждение каждого использования выбранного credential. Это не заменяет серверную авторизацию, но делает границу процесса явной и не передаёт credential agent.

К AI coding agent нужно относиться строже, чем к интерактивному терминалу: он способен быстро выполнить множество команд и может последовать вредной инструкции. По умолчанию дайте ему доступ к development. Если нужен staging, используйте identity только для staging и отдельный одобренный запуск. Доступ к production рассматривайте как отдельную операцию с указанной целью и узким назначением.

## Сбой обычно начинается с безобидного исключения

Представьте команду с одним SSH-ключом `ops`, который принимают хосты development, staging и production. Инженер загружает этот ключ в SSH-agent на время обслуживания production. Позже локальный помощник сборки подключается по SSH к staging, чтобы собрать журналы.

У помощника нет явной настройки `IdentityFile`, а `IdentitiesOnly` не задан. OpenSSH перебирает identities из agent. Общий ключ `ops` подходит, потому что staging его принимает. Теперь у помощника есть staging-сессия, а identity этой сессии также работает в production.

Следует вторая ошибка. На staging-хосте включена пересылка agent, потому что месяц назад кому-то понадобилось разовое подключение. Процесс на этом хосте может запрашивать подписи через пересланный agent. Он попадает на production-хост с той же identity `ops`. Исходный инженер одобрил сессию обслуживания production, но несвязанный помощник и staging-хост унаследовали её полномочия.

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

Разделённая схема разрывает цепочку сразу в нескольких местах. Помощник использует только staging-credential. Production отвергает его. Пересылка agent отключена. Production-identity доступна лишь явно одобренному production-процессу. Каждая из этих мер помогает сама по себе, а вместе они заставляют неправильное подключение завершиться на раннем этапе.

## Ротация и аварийный отзыв требуют отработанного порядка

Ротация работает, когда вы сначала добавляете замену, проверяете точный маршрут, а затем удаляете старую identity. Ротация production часто завершается сбоем, если проверяют только ноутбук, а настоящий deployer подключается с другого аккаунта, из другой сети или через другой runner автоматизации.

Для обычной ротации используйте такой порядок:

1. Создайте credential для замены с тем же узким scope, что и у старого.
2. Добавьте его открытый ключ или авторизацию сертификата нужному аккаунту и на нужные хосты.
3. Проверьте настоящую команду из настоящего процесса, включая jump host.
4. Удалите старую авторизацию и убедитесь, что старый credential теперь не работает.
5. Запишите fingerprint замены, владельца, scope и дату удаления в реестр доступа.

Сохраните аварийный путь, но не превращайте его во второй постоянный credential администратора, скопированный на каждую рабочую станцию. Храните его отдельно, ограничьте круг активирующих его людей и проверяйте в контролируемых условиях. Аварийный процесс, которым никто не пользовался, во время сбоя превращается в лихорадочное угадывание.

Аварийный отзыв отличается от обычной ротации. Если production-credential мог раскрыться, немедленно удалите его открытый ключ или прекратите принимать сертификат, а затем замените credential. Не ждите планового окна: атакующий не будет соблюдать ваш календарь. Цена этого решения - возможный сбой работы, поэтому узкие credentials так важны: отзыв production-identity развёртывания не должен останавливать development.

## Проверяйте запрещённый доступ и изучайте свидетельства

Разделение доступа завершено только тогда, когда неправильный credential не работает и вы можете объяснить почему. После каждого существенного изменения проводите намеренную отрицательную проверку: используйте development-identity против production-хоста и убедитесь, что аутентификация открытым ключом завершается ошибкой. Затем проверьте правильный credential и изучите имя аккаунта и цель в записи подключения.

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

```sh
ssh -vvv -o IdentitiesOnly=yes \
  -i ~/.ssh/identities/id_ed25519_dev_alex \
  admin@prod-api-01.internal
```

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

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

Проверяйте дрейф границ: development-ключ, добавленный в production-аккаунт, старый credential развёртывания, который всё ещё принимается, production-ключ, загруженный в универсальный agent, или правило wildcard, переопределяющее `IdentitiesOnly`. Такие изменения часто появляются как временные исправления. Временный SSH-доступ имеет свойство переживать чрезвычайную ситуацию, которая его создала.

Начните с инвентаризации всех открытых ключей, принимаемых на production-хостах, и подпишите каждый по владельцу, процессу, назначению и scope. Запись, которую нельзя описать всеми четырьмя характеристиками, не должна оставаться авторизованной.
