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: [email protected] 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-сертифікатами. Ось деякі з них:

BLESS від Netflix з усіх цих рішень здається найбільш цікавим. В першу чергу із-за того, що не потрібно створювати сервер, який буде займатися підписування сертифікатів. Адже в такому випадку потрібно зрозуміти, як убезпечити (сертифікатів ще немає, щоб через них ходити на цей сервер) і створити надмірність, щоб він не став точкою відмови всієї інфраструктури. Використання AWS Lambda з регіонами AWS дозволяє уникнути цих двох проблем. Але якщо підхід цікавий, то сам BLESS має дещо не дуже зручних речей:

  1. BLESS написаний на Python. Сам по собі мову Python не є чимось поганим (хороший мова), але BLESS вимагає складання проекту. Оскільки там йде збірка нативних бібліотек, це треба робити в Docker-контейнері. Але навіть і після цього немає гарантії, що у вас все вийде: залежно можуть поламатися (знову ж мова тут ні при чому). У збірку заздалегідь потрібно не забути додати CA-ключ, самому зашифрувати його пароль і сам файл. Все це не дозволяє так просто використовувати BLESS.
  2. Розгорнути BLESS не так легко і зрозуміло, як здається. Особливо якщо вам потрібно ще додати підтримку KMSAuth. Документація не дуже дружелюбна в цьому плані.
  3. У BLESS немає хорошого офіційного клієнта. Є невеликий Python-скрипт, і на цьому все. Є, звичайно, сторонні рішення , що вже робить виклик BLESS-функції зручніше. Але навіть сторонні рішення не вирішують проблему, коли ти як користувач повинен перемикатися між проектами (коли у тебе один проект, наприклад Netflix, таких проблем не відчуваєш).
  4. 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:... [email protected] (RSA)
256 SHA256:... [email protected] (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 не містить всього функціоналу, що я задумав для нього:

Але я сподіваюся, що цей продукт вже зможе вам допомогти більш безпечно працювати з SSH.

Формат статті не дозволяє за один раз розкрити важливі аспекти роботи з SSH-сертифікатами і Certonid, такі як SSH-хост-сертифікати, critical options і extensions у сертифікатах, налаштування та робота з KMSAuth, значення налаштувань в конфігах Certonid та інше. Якщо читачам ця тема цікава, я постараюся продовжити розповідь в наступній статті.

Дякую за увагу! Успіхів вам з SSH-безпекою!

Опубліковано: 19/11/19 @ 11:00
Розділ Різне

Рекомендуємо:

Шукаємо причини овертаймів в команді: чек-лист для менеджера
Product Marketing дайджест #1: стратегія Ahrefs, 63 ради щодо збільшення конверсії
Security Sandwich: інструкція з приготування
Як у SoftServe втілили концепцію Mixed Reality, у якій віртуальні фрази об'єкти можна відчути на дотик
Union-find: алгоритм, застосування та аналіз складності