Примеры грамотного применения SSH-шаблонов
Шаблоны для сертификатов SSH действуют аналогично шаблонам X.509: это JSON-файлы, написанные в Go text/template. Они применяются для настройки SSH-сертификатов, которые выдаёт step-ca. В этом переводе рассказывается, что представляют собой эти шаблоны и как их можно использовать.
SSH-сертификаты — очень мощный инструмент. Первоначально в удостоверяющем центре step-ca
мы реализовали только минимальный набор функций для аутентификации по сертификатам пользователя и хоста. Затем добавили шаблоны сертификатов X.509, а ещё в августе прошлого года — и SSH-шаблоны, в версии 0.15.2. Наконец, мы задокументировали эту функцию и готовы о ней рассказать.
Шаблоны для сертификатов SSH действуют аналогично шаблонам X.509: это JSON-файлы, написанные в Go text/template
. Они применяются для настройки SSH-сертификатов, которые выдаёт step-ca
. Давайте посмотрим, что представляют собой эти шаблоны и как их можно использовать.
По умолчанию шаблон пользовательского SSH-сертификата выглядит так:
{
"type": {{ toJson .Type }},
"keyId": {{ toJson .KeyID }},
"principals": {{ toJson .Principals }},
"extensions": {{ toJson .Extensions }},
"criticalOptions": {{ toJson .CriticalOptions }}
}
А вот SSH-сертификат, выданный по этому шаблону:
$ step ssh inspect id_ct-cert.pub
id_ct-cert.pub:
Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
Public key: ECDSA-CERT SHA256:iczSh1XiBBE36yfJcDidgp6fqY3qWx1RtEwFfAN9jDs
Signing CA: ECDSA SHA256:MKwRQ/SDKk/pCJbbCk5bfhZACjSjv7uZXLyc5n4Wx6k
Key ID: "carl@smallstep.com"
Serial: 2831574724231262409
Valid: from 2020-11-17T16:48:11 to 2020-11-18T08:49:11
Principals:
carl
carl@smallstep.com
Critical Options: (none)
Extensions:
permit-X11-forwarding
permit-agent-forwarding
permit-port-forwarding
permit-pty
permit-user-rc
Он позволяет юзеру carl
(или carl@smallstep.com
) пройти аутентификацию на любом SSH-хосте, который доверяет моему SSH CA. Сертификат включает в себя некоторые основные расширения:
permit-x11-forwarding
: Разрешает переадресацию X11 (с помощьюssh -X
) для запуска удалённых программ X11 на локальном дисплее.permit-agent-forwarding
: Разрешает переадресацию агента (с помощьюssh -A
) для пересылки ключей из локального агента SSH на удалённый хост (подробнее про агента SSH см. здесь).permit-port-forwarding
: Разрешает переадресацию портов (туннель) с локального на удалённый порт (ssh -L
) или с удалённого на локальный (ssh -R
).permit-pty
: Очень важное расширение. Если хотите открыть интерактивную сессию в консоли, хост должен выделить вам pty (псевдо-tty). В противном случае не предусмотрено никакой интерактивности. Например, для проверки SSH-аутентификации на GitHub можно запуститьssh -T git@github.com
(-T
отключает запрос на pty).permit-user-rc
: Запуск личного RC-файла после подключения (находится в~/.ssh/rc
на удалённом хосте).
Сертификаты пользователя и хоста поддерживают расширения и критические параметры, но OpenSSH не определяет никаких встроенных расширений или критических параметров для сертификатов хоста. Таким образом, всё самое интересное происходит именно с сертификатами пользователя, поэтому в данной статье рассмотрим только их.
Примеры шаблонов сертификатов
Внесём несколько изменений в дефолтный шаблон.
Запрещаем переадресацию агента и портов
Если пользователи подключаются к внутренним хостам через хост-бастион, то хорошо бы запретить перенаправление портов по соображениям безопасности. Вы же не хотите, чтобы юзеры перенаправили трафик с продакшн-сервера MySQL на свой localhost. Так же и переадресация агента сопряжена с риском для безопасности. Вот шаблон, который просто удаляет эти два расширения из сертификатов SSH:
{
"type": {{ toJson .Type }},
"keyId": {{ toJson .KeyID }},
"principals": {{ toJson .Principals }},
"extensions": {
"permit-x11-forwarding": "",
"permit-pty": "",
"permit-user-rc": ""
},
"criticalOptions": {{ toJson .CriticalOptions }}
}
Встраиваем директиву force-command
ForceCommand
— это серверная директива конфигурации SSHD, которая вместо интерактивного терминала запускает на хосте альтернативную команду. Но с тем же эффектом можно встроить force-command
прямо в сертификат — в раздел Critical Options:
. Может пригодиться для служебных аккаунтов, которым необходимо выполнить только одну команду, например, запустить задание в удалённой системе.
Ограничиваем соединения по адресам
Чтобы ограничить область использования сертификата, в него можно встроить список разрешённых IP-адресов (блоков CIDR).
Вот шаблон сертификата, который использует и source-address
, и force-command
.
{
"type": {{ toJson .Type }},
"keyId": {{ toJson .KeyID }},
"principals": {{ toJson .Principals }},
"extensions": {{ toJson .Extensions }},
"criticalOptions": {
"force-command": "echo \"Hello World\"",
"source-address": "10.20.30.0/24,1.1.1.1/32"
}
}
Это нормальный пример, но здесь список IP жёстко зафиксирован и не изменяется. А нам обычно хочется использовать разные списки адресов для разных пользователей. Давайте попробуем…
Вставляем разные значения для разных юзеров
Очевидно, пользователям нельзя давать право редактировать диапазон адресов. Следовательно, динамические значения должны поступать из надёжного источника.
Для этого в step-ca
можно через поставщика OpenID Connect (OIDC) настроить провайдера OAuth на добавление к токену нестандартных требований (custom claim), содержащих блоки адресов CIRD, которые мы хотим добавить в сертификат этого пользователя.
Поставщик OIDC представляет собой идеальный способ выдачи SSH-сертификатов в step-ca. В статье DIY Single Sign-On for SSH я рассказывал, как настроить SSH CA, чтобы он выдавал краткосрочные SSH-сертификаты по ID-токенам от доверенного провайдера OAuth. Если step-ca
настроен как доверенный клиент OAuth, он будет считывать поле email
из токена ID и извлекать оттуда список участников SSH-сертификата (например, по полю carl@smallstep.com
сгенерируются сертификаты для carl
и carl@smallstep.com
).
Но OIDC позволяет через шаблоны считать из токенов ID и другие поля. Вот где начинается настоящая магия. Таким образом, добавляем в каталог пользователей на стороне провайдера идентификации отдельное поле source_address
— и отражаем его в нашем ID-токене. Затем через шаблон SSH можно ввести значение из токена в сертификат. Вот шаблон:
{
"type": {{ toJson .Type }},
"keyId": {{ toJson .KeyID }},
"principals": {{ toJson .Principals }},
"extensions": {{ toJson .Extensions }},
{{ if .Token.source_address }}
"criticalOptions": {
"source-address": "{{ .Token.source_address }}"
}
{{ else }}
"criticalOptions": {{ toJson .CriticalOptions }}
{{ end }}
}
Аутентификация на GitHub по сертификату
Рассмотрим ещё один пример custom claim. С помощью GitHub Enterprise Cloud или GitHub Enterprise Server можно настроить GitHub на использование SSH-сертификатов. В частности, GitHub будет доверять вашему центру сертификации SSH. Но чтобы всё заработало, нужно создать для каждого пользователя отдельный SSH-сертификат с расширением login@github.com
, в котором прописывается имя пользователя на GitHub. С помощью этого расширения сертификат аутентифицирует пользователя на GitHub Enterprise. И это здорово: один и тот же сертификат позволяет и подключиться к вашему серверу по SSH, и запушить код на GitHub.
Вот шаблон сертификата с поддержкой индивидуального расширения GitHub:
{
"type": {{ toJson .Type }},
"keyId": {{ toJson .KeyID }},
"principals": {{ toJson .Principals }},
"criticalOptions": {{ toJson .CriticalOptions }},
{{ if .Token.ghu }}
"extensions": {
"login@github.com": {{ toJson .Token.ghu }}
}
{{ else }}
"extensions": {{ toJson .Extensions }}
{{ end }}
}
Для использования шаблона нужно добавить к токенам идентификации OIDC индивидуальное требование ghu
(“GitHub Username”). Давайте подробно рассмотрим, как создать этот custom claim с помощью вашего провайдера OAuth.
Регистрация заявления у провайдера идентификации
Не все провайдеры идентификации поддерживают индивидуальные требования, но если поддержка всё-таки есть, то процесс довольно похож. Вот как это делается с помощью Okta:
- Добавьте приложение OAuth в Okta и установите доверие к нему у поставщика OIDC в
step-ca
, как описано в статье DIY SSO for SSH.
- Добавьте новое поле в каталог пользователей Okta (например,
GitHub Username
) - Добавьте к OIDC-токену индивидуальное требование, например, с сокращённым названием
ghu
- Теперь заполняем поле для тестового юзера и проверяем требование. В Okta есть инструмент тестирования токена ID. Или можно использовать
step
для проверки всего потока OAuth:
OIDC_ENDPOINT="https://[your organization].okta.com/oauth2/default/.well-known/openid-configuration" CLIENT_ID="[your OAuth client ID]" CLIENT_SECRET="[your OAuth client secret]" step oauth --oidc --provider $OIDC_ENDPOINT \ --client-id $CLIENT_ID --client-secret $CLIENT_SECRET \ --listen=":10000" --bare | step crypto jwt inspect --insecure
- Наконец,
настройте step-ca
для использования этого шаблона. Конфигурация поставщика должна ссылаться на файл шаблона:
{ "provisioners": [ { "type": "OIDC", "name": "Okta", "clientID": "[your OAuth client ID]", "clientSecret": "[your OAuth client secret]", "configurationEndpoint": "https://[your organization].okta.com/oauth2/default/.well-known/openid-configuration", "listenAddress": ":10000", "options": { "ssh": { "templateFile": "templates/certs/ssh/github.tpl" } } }, ... ] }