Certonid — SSH центр сертификации, который работает на AWS Lambda
Всем привет! Меня зовут Алексей, я разработчик/девопс/подкастер, и в этой статье я хочу вам рассказать о своем проекте Certonid — серверлесс-SSH-центре сертификации (serverless SSH certificate authority). Этот инструмент может помочь решить проблему менеджмента доступа к Linux-серверам по SSH. Давайте начнем по порядку.
SSH-сертификаты
SSH вездесущ. Это де-факто стандарт для удаленного администрирования *nix-систем. Когда девопс настраивает Linux-сервер, то обычно создается пара учетных записей с паролями. Локальное управление учетными записями хорошо работает с небольшими группами серверов, но по мере роста продукта требуется создавать центральную систему аутентификации, такую как LDAP и/или Kerberos, чтобы избежать ручного управления учетными записями на каждом сервере. При дальнейшем росте девопс может прийти к выводу, что центральная система аутентификация — единственная и потенциально разрушительная точка отказа всей системы. Как только она выйдет из строя, то все потеряют доступ ко всему (если только не создать учетные записи с прямым, в обход системы аутентификации, доступом, что может быть небезопасно). Блокирование вашей собственной системы — одна из худших вещей, которые могут случиться во время инцидента. Например, если перебои в работе службы повредили вашу систему аутентификации, то вы не сможете войти и исправить ее.
В дополнение к этим рискам выбор SSH-аутентификации с открытым ключом (SSH public key authentication) для ваших разработчиков/девопсов означает необходимость управления их открытыми ключами на всех ваших серверах. Также нередко безопасность системы ставится под угрозу, если есть неизвестные открытые ключи в файлах authorized_keys (в такой файл пишутся открытые ключи, через которые по SSH можно получить доступ к системе). Кроме того, authorised_keys требует определения доверия по отдельной паре ключей, которая не масштабируется.
Для решения проблем с SSH-аутентификацией с помощью открытого ключа (SSH public key authentication) можно перейти к SSH-аутентификации с помощью сертификатов (SSH certificate authentication). Давайте посмотрим, как это работает.
Для начала создадим свой собственный CA (certificate authority, центр сертификации), который, по сути, представляет собой обычную пару ключей:
$ mkdir sshca && cd sshca $ ssh-keygen -C CA -f ca-key Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in ca-key. Your public key has been saved in ca-key.pub.
Ssh-keygen-утилита попросит ввести пароль на ваш ключ, что я советую всегда делать. Можно создать ключ и без пароля, но я бы не оставлял этот ключ без него. Пароль в этом случае используется для шифрования закрытого ключа. Это само по себе делает файл ключа бесполезным для злоумышленника (хотя бы на какое-то время). Это, конечно, не означает, что если закрытый ключ был скомпрометирован, то его не нужно будет менять (это придется делать в любом случае). Просто у вас появится фора, пока злоумышленник будет подбирать пароль, чтобы расшифровать ваш закрытый ключ.
Сейчас у вас есть два файла: «ca-key» (закрытый ключ) и «ca-key.pub» (открытый ключ). Теперь надо распространить ca-key.pub по всем серверам, к которым требуется доступ по SSH через сертификаты. Поскольку вы распространяете открытый ключ, то можно не беспокоиться о безопасности его передачи: канал его распространения необязательно шифровать. Для нашего примера давайте разместим его в «/etc/ssh/ca-key.pub» на серверах.
Следующий этап заключается в конфигурации SSH-демонов на серверах, чтобы они доверяли этому ключу, путем добавления/изменения строчки в конфиге «/etc/ssh/sshd_config» (не забудьте после изменений перезагрузить SSH-демон):
TrustedUserCAKeys /etc/ssh/ca-key.pub
Теперь, когда у вас есть цепочка доверия (chain of trust), вы можете начать создавать SSH-сертификаты. В идеале ваш CA должен быть очень защищенным сервером, к которому доступ может получить только команда, которая занимается безопасностью в продукте (security team). Одна очень важная практика безопасности состоит в том, что закрытые ключи никогда не должны покидать систем, на которых они были созданы, независимо от того, насколько безопасен канал передачи данных.
Далее уже на компьютере разработчика генерируем ему SSH-ключи (если их нет или вы хотите использовать отдельные ключи):
$ ssh-keygen -t ecdsa # или “ssh-keygen -t rsa” Generating public/private ecdsa key pair. Enter file in which to save the key (/Users/leo/.ssh/id_ecdsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/leo/.ssh/id_ecdsa. Your public key has been saved in /Users/leo/.ssh/id_ecdsa.pub.
Теперь в «.ssh/» каталоге у нас есть файлы «id_ecdsa» (закрытый ключ) и «id_ecdsa.pub» (открытый ключ). Копируем открытый ключ на CA-сервер. Канал передачи данных неважен. Просто никуда не передавайте закрытый ключ «id_ecdsa».
Наконец, на CA-сервере создаем SSH-сертификат для открытого ключа разработчика:
$ ssh-keygen -s ca-key -I leo -n deployer -V +1w -z 1 id_ecdsa.pub Enter passphrase: Signed user key id_ecdsa.pub: id "leo" serial 1 for deployer valid from 2019-11-07T00:32:00 to 2019-11-14T00:33:44
Что тут происходит? По сути, мы подписываем «id_ecdsa.pub» через ca-key. Идентификатор сертификата будет leo, и единственным principal будет deployer. Сертификат действителен в течение одной недели и имеет серийный номер 1. Теперь у вас должен быть файл id_ecdsa-cert.pub. Скопируйте его обратно на компьютер разработчика и поместите в папку .ssh/. Канал передачи данных и здесь неважен, это общедоступная информация, а сам сертификат не работает без соответствующего закрытого ключа (тот самый id_ecdsa). Поскольку мы не настроили серверы на использование определенного набора principal, стандартная конфигурация sshd позволит этому сертификату войти в систему под именем любого пользователя (который присутствует в системе). Поскольку я использовал -n deployer для создания сертификата, то могу войти в систему как deployer-юзер. Если у вас нет специальной схемы авторизации на серверах, этого для вас может быть достаточно.
Давайте глянем информацию по сертификату с помощью ssh-keygen:
$ ssh-keygen -Lf id_ecdsa-cert.pub id_ecdsa-cert.pub: Type: Данный адрес e-mail защищен от спам-ботов, Вам необходимо включить Javascript для его просмотра. user certificate Public key: ECDSA-CERT SHA256:Kz/8gC5dKLQaYsiAoQwnf7wAbEJLQ0R4TD4iCHwK9Bg Signing CA: RSA SHA256:Tk2tXG7mqDJS8Pzj8RiA3MgpqlgOUYG2i3Ju7wYN7QM Key ID: "leo" Serial: 1 Valid: from 2019-11-07T00:32:00 to 2019-11-14T00:33:44 Principals: deployer Critical Options: (none) Extensions: permit-X11-forwarding permit-agent-forwarding permit-port-forwarding permit-pty permit-user-rc
Как уже было сказано, у сертификата есть время жизни. В случае инцидента — ошибки, кражи, неправильного использования — скомпрометированные учетные данные SSH-сертификата истекают автоматически, без вмешательства, даже если инцидент остался незамеченным. Можно создавать сертификаты, время жизни которых будет несколько минут, потому что SSH-сервер не выбросит пользователя из системы, если сертификат истек, пока он к ней подключен.
Теперь, когда у нас есть сертификат и закрытый ключ, можно подключиться как пользователь deployer на SSH-сервер, который доверяет нашему CA:
$ ssh deployer@system-which-trust-ca-key
Как только произойдет подключение к серверу, в журнале аутентификации можно будет заметить такую строчку:
Nov 6 22:55:11 example sshd[1899]: Accepted publickey for deployer from 176.14.529.36 port 56223 ssh2: ED25519-CERT ID leo (serial 1) CA RSA SHA256:...
Прекрасно видно, что даже при входе на сервер от имени deployer система может идентифицировать сертификат, используемый для аутентификации. В этом случае сертификат с идентификатором leo. Это означает, что использование правильного -I с ssh-keygen очень важно, потому что оно идентифицирует, кому принадлежит сертификат. Также рекомендуется использовать уникальный серийный номер (тут serial 1), чтобы вы могли идентифицировать каждый выданный отдельный сертификат. Кстати, уникальные серийные номера обязательны, если вы хотите использовать SSH-параметр RevokedKeys в конфиге для отзыва скомпрометированных сертификатов с закрытыми ключами.
Надеюсь, теперь вам ясно, как работают SSH-сертификаты. Хочу заметить, что это решение не какая-то новая хипстерская поделка — аутентификация с использованием сертификатов была добавлена в OpenSSH 5.4 почти десять лет назад.
Автоматизация
Теперь рассмотрим более серьезный вопрос: как это автоматизировать? Держать отдельного человека, который будет сидеть за своим компьютером и подписывать сертификаты вручную, — кажется не очень хорошим решением. Существует множество инструментов для управления SSH-сертификатами. Вот некоторые из них:
- netflix/bless — решение от Netflix. Работает на AWS Lambda (serverless) и использует AWS IAM. Написано на Python;
- nsheridan/cashier — решение от Intercom. Представляет собой сервер и клиент, написанный на Golang;
- uber/pam-ussh — подключаемый модуль аутентификации (PAM) от Uber, который написан на Golang;
- hashicorp/vault — решение от HashiCorp для хранения секретов. Содержит SSH secrets engine с поддержкой сертификатов.
BLESS от Netflix из всех этих решений кажется наиболее интересным. В первую очередь из-за того, что не нужно создавать сервер, который будет заниматься подписыванием сертификатов. Ведь в таком случае нужно понять, как обезопасить (сертификатов еще нет, чтобы через них ходить на этот сервер) и создать избыточность, чтобы он не стал точкой отказа всей инфраструктуры. Использование AWS Lambda с регионами AWS позволяет избежать этих двух проблем. Но если подход интересен, то сам BLESS имеет несколько не очень удобных вещей:
- BLESS написан на Python. Сам по себе язык Python не является чем-то плохим (хороший язык), но BLESS требует сборки проекта. Поскольку там идет сборка нативных библиотек, это надо делать в Docker-контейнере. Но даже и после этого нет гарантии, что у вас все получится: зависимости могут поломаться (опять же язык тут ни при чем). В сборку заранее нужно не забыть добавить CA-ключ, самому зашифровать его пароль и сам файл. Все это не позволяет так просто использовать BLESS.
- Развернуть BLESS не так легко и понятно, как кажется. Особенно если вам нужно еще добавить поддержку KMSAuth. Документация не очень дружелюбная в этом плане.
- У BLESS нет хорошего официального клиента. Есть небольшой Python-скрипт, и на этом все. Имеются, конечно, сторонние решения, что уже делает вызов BLESS-функции удобнее. Но даже сторонние решения не решают проблему, когда ты как пользователь должен переключаться между проектами (когда у тебя один проект, например Netflix, таких проблем не ощущаешь).
- BLESS заточен для работы на бастион-хосте. Бастион (Bastion host) — так называют специально отведенный компьютер в сети, обычно расположенный на внешней стороне демилитаризованной зоны (ДМЗ) организации. Через него попадают уже на другие серверы, которые находятся в закрытой сети. Я выскажу непопулярное мнение, идущее вразрез с нынешними best security practices: вам, вероятнее всего, не нужен бастион, и он может принести больше вреда, чем пользы. Я могу привести в качестве доказательства много пунктов, которые покажут, что этот узел не добавляет надежности и безопасности, но статья не об этом. Единственный плюс, который я не буду оспаривать: бастион замедляют атаку, особенно автоматизированную. Это как купить очень хорошую входную дверь: невскрываемых дверей нет, но более защищенная доставит больше хлопот грабителю при вскрытии, и поэтому многие просто могут отказаться ее вскрывать. Так вот, в большинстве продуктов нет бастион-хоста, и доступ по SSH нужно получить с машины пользователя. В таком случае настройка и развертывание клиента, что будет помогать в этом, должны быть простой процедурой.
После нескольких не очень успешных попыток завести BLESS (в комплексе, а не только AWS Lambda) за несколько выходных был написан «свой велосипед». Certonid — это как раз тот же SSH Serverless CA. Написан на Golang, за счет чего сразу предоставляются собранные бинарные файлы, которые вам не требуется дополнительно подготавливать. Состоит он из двух частей — CLI и серверлесс-части. Подготовить его достаточно просто. Вам нужно скачать CLI (выбрать под вашу систему) и серверлесс-часть (только одна версия — AWS Lambda работает на Linux). Далее создать zip-файл, в который положить серверлесс serverless.linux.amd64 (лучше назвать файл serverless), ваш ca-key и certonid.yml. В конфиге нужно будет указать, как получить доступ к ca-key (сам файл можно зашифровать симметричным шифрованием или с использованием AWS KMS) и как получить пароль для ключа (Certonid работает только с закрытыми ключами, у которых есть пароль), который будет тоже зашифрован симметричным шифрованием или с использованием AWS KMS. Для упрощения этой работы у CLI есть вспомогательные функции для шифрования строк или файлов. Пример конфига:
ca: storage: file path: ca.pem encrypted: encryption: aws_kms region: us-east-1 passphrase: encryption: aws_kms region: us-east-1 content: AQICAHhBwiHijA5XW9EyanTVga4XbbwEVCmBLSUiWIxrCrxrUwGGt8JapxlfiJljay3FycLOAAAAZjBkBgkqhkiG9w0BBwagVzBVAgEAMFAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMtZxOyGi2foFM+y9SAgEQgCOY1N4sMr5RIiyQ4/8yloRIAi6vWaK3n/jEdgPfn3bdJjrkNQ== certificates: user: max_valid_until: 2h additional_principals: - "ubuntu" - "ec2-user" critical_options: - "source-address 0.0.0.0/0" extensions: - "permit-X11-forwarding" - "permit-agent-forwarding" - "permit-port-forwarding" - "permit-pty" - "permit-user-rc" logging: level: info
Далее этот zip-файл загружаем в AWS Lambda. AWS Lambda Runtime — Go 1.x. AWS Lambda Handler должен быть именем файла серверлесс-компонента Certonid в zip-файле (в примере файл serverless). Не забудьте, что этой функции требуется доступ к ключам AWS KMS, если вы через него зашифровали пароли и, по желанию, сам CA-ключ.
Памяти и времени выполнения на эту функцию много не потребуется (128 Мбайт и 10 с должно хватить с запасом). Для примера строчка из CloudWatch-логов:
REPORT RequestId: d7e99280-7860-426e-a6e1-e80d83176f83 Duration: 3223.70 ms Billed Duration: 3300 ms Memory Size: 128 MB Max Memory Used: 58 MB Init Duration: 124.16 ms
После этого нужно настроить Certonid CLI. Для вызова функции AWS Lambda используется AWS Identity and Access Management (IAM): каждому пользователю, которому требуется доступ по SSH на серверы, создается пользователь в IAM. Если человек покидает проект, его аккаунт блокируется на AWS, и он более не сможет запрашивать новые сертификаты, чтобы попадать на серверы (старые перестанут работать по истечении указанного вами времени жизни). Совет от меня: если вы уже используете AWS для проекта, то лучше для Certonid AWS Lambda и IAM-менеджмента создать отдельный AWS-аккаунт, чтобы не смешивать сущности и доступы уже самого продукта. Конфиг CLI по умолчанию хранится в $HOME/.certonid.yml. Пример конфига:
certificates: examplecom: public_key_path: ~/.ssh/id_ed25519.pub username: leopard runner: aws valid_until: 4h aws: profile: aws-profile region: us-east-1 function_name: CertonidFunction
Секция certificates содержит разные проекты/функции с сертификатами, поэтому один CLI может работать с несколькими серверлесс-центрами сертификации.
Для настроек доступа можно в конфиге Certonid указать AWS Access Key ID и Secret Access Key через переменные окружения или (я пользуюсь таким вариантом) настроить AWS CLI профили и внести нужный профиль для Certonid в конфигурации (в примере так сделано).
После этого с помощью команды certonid gencert можем получить сертификат на наш открытый ключ:
$ certonid gencert examplecom INFO[2019-11-07T17:57:20+02:00] Signing public key certificate=/Users/leo/.projects_certs/examplecom-cert.pub public key=/Users/leo/.ssh/id_ed25519.pub runner=aws INFO[2019-11-07T17:57:25+02:00] Certificate generated and stored certificate=/Users/leo/.projects_certs/examplecom-cert.pub public_key=/Users/leo/.ssh/id_ed25519.pub valid until="2019-11-08 15:57:22 +0000 UTC"
Если команду запустить заново, то она проверит существующий сертификат, и если он все еще работает, то CLI просто завершит свою работу и лишний раз не будет вызывать AWS Lambda (не забываем, что эти вызовы стоят денег).
$ certonid gencert examplecom INFO[2019-11-07T18:00:35+02:00] Current certificate still valid. Exiting... certificate=/Users/leo/.projects_certs/examplecom-cert.pub valid until="2019-11-08 15:57:22 +0000 UTC"
Больше по командам можно узнать из certonid -h или Вики.
Интеграция с SSH
Есть одно условие для работы сертификатов: сертификат должен лежать в той же папке, что и закрытый ключ с именем файла <имя закрытого ключа>-cert.pub. В таком случае SSH agent при добавлении закрытого ключа автоматически подхватит в хранилище сертификат. Но это неудобно, если вы используете один и тот же закрытый ключ для разных проектов (нужно перезаписывать файл сертификата и потом добавлять его в SSH-агент).
Certonid по умолчанию складывает сертификаты в отдельную папку, чтобы у вас была возможность использовать один закрытый ключ (если требуется) для разных проектов, не переписывая каждый раз один файл. Конечно же, через конфиг это можно поменять и сохранять его там, где вам нравится. Но тогда как эти сертификаты использовать, раз они хранятся не по формату OpenSSH?
Другой вопрос, который у вас может созреть в голове: «Это все хорошо, но мне теперь каждый раз запускать эту поделку, перед тем как подключиться по SSH к серверу?» Замечание очень верное, и это неудобство нужно как-то решать.
Первый вариант решения проблемы заключается в создании конфигурации для вашего SSH-клиента. Допустим, нам нужно подключаться к доменам *.example.com через сертификат examplecom. В файл конфигурации ($HOME/.ssh/config) мы можем добавить:
Match Host *.example.com exec "certonid gencert examplecom" Port 22 User deployer IdentityFile ~/.ssh/id_ed25519 CertificateFile ~/.projects_certs/examplecom-cert.pub PasswordAuthentication no
Как видите, SSH-конфиг поддерживает для сравнения exec-опцию, которая выполнит команду, и если она завершится успешно (код выхода ноль), то условие считается истинным для конфигурации. Вот в этот параметр можно добавить команду certonid gencert examplecom. Поскольку сертификат лежит в другом месте, его можно указать через параметр CertificateFile. После такой конфигурации можно просто писать в консоли и подключаться к серверам:
$ ssh web1.example.com $ ssh web4.example.com
Но бывают случаи, когда определенный софт не умеет работать с SSH-конфигом, а на <имя закрытого ключа>-cert.pub ему может быть все равно. Если этот софт поддерживает работу с SSH-агентом, сертификат можно добавить в него. Пример команды прост:
$ certonid gencert examplecom --add-to-ssh-agent ~/.ssh/id_ed25519
Поскольку сертификат не добавить в SSH-агент без закрытого ключа, его придется указать как значение для команды —add-to-ssh-agent. Этот параметр можно добавить в yml-конфиг, но такой вариант не подойдет тем, у кого закрытые ключи зашифрованы паролем, потому что при каждом запуске с такой командой Certonid будет спрашивать тот самый пароль. После этого можно проверить сертификат в агенте:
$ ssh-add -l 4096 SHA256:... leo@Alexeys-MacBook-Air.local (RSA) 256 SHA256:... leo@Alexeys-MacBook-Air.local (ED25519) 256 SHA256:... leopard_1573142242 [Expires 2019-11-08 17:57:22 +0200 EET] (ED25519-CERT)
При этом Certonid добавил сертификат со сроком его жизни в SSH-агент. Поэтому, как только сертификат станет недействительным, он автоматически исчезнет из агента (мусор за собой нужно убирать). После этого можете подключаться к серверам или добавлять в SSH-конфиг:
Host *.example.com Port 22 User deployer ForwardAgent yes PasswordAuthentication no
В заключение
На текущий момент Certonid не содержит всего функционала, что я задумал для него:
- нет автоматического переключения на другие serverless, если регион упал;
- нет тестов (что немаловажно для последующей разработки);
- хочется добавить поддержку Google Cloud и Azure (если с первым все понятно, то у второго нет поддержки Golang);
- надо еще улучшать документацию.
Но я надеюсь, что этот продукт уже сможет вам помочь более безопасно работать с SSH.
Формат статьи не позволяет за один раз раскрыть важные аспекты работы с SSH-сертификатами и Certonid, такие как SSH-хост-сертификаты, critical options и extensions в сертификатах, настройка и работа с KMSAuth, значение настроек в конфигах Certonid и прочее. Если читателям эта тема интересна, я постараюсь продолжить рассказ в следующей статье.
Благодарю за внимание! Успехов вам с SSH-безопасностью!