HashiCorp Vault имеет в своём арсенале SSH secrets engine, который позволяет организовать защищённый доступ к вашим машинам по ssh, через создание клиентских сертификатов и одноразовых паролей. Про последнее – создание одноразовых паролей (OTP) – мы и поговорим в этой статье.
Как это работает?

Клиент генерирует OTP для нужного пользователя и хоста, далее обычным ssh username@host подключается к серверу. Сгенерировать ключ можно напрямую через vault, используя cli, API или веб-интерфейс, или через вашу внутреннюю систему, которая сама будет дёргать vault API и создавать OTP.
Когда SSH-сервер получает запрос на аутентификацию, он вызывает PAM (Pluggable Authentication Modules) модуль, который в процессе выполняет внешнюю команду. Этой внешней командой является vault-ssh-helper, который, в свою очередь, стучится в ваш Vault-кластер для проверки токена, отправленного клиентом. Если всё ок, то доступ предоставляется, а токен инвалидируется.
Установка и настройка
Вся настройка достаточно быстрая и состоит из двух этапов: необходимые манипуляции внутри кластера Vault и на самом host'е.
Как всё настроить подробно описано в официальной статье от HashiCorp. Инструкция ниже это по сути её перевод. Вы можете это пропустить и перейти сразу к нюансам.
Манипуляции внутри Vault'а
Включаем SSH secrets engine.
vault secrets enable ssh
Создаём роль, которая будет использована для генерации OTP ключей для клиентов. Указываем дефолтного юзера и список разрешённых. А также в cidr_list задаём список адресов, к которым будут подходить ключи.
Рекомендуется создавать по одной роли для каждого пользователя.
vault write ssh/roles/otp_role \ key_type=otp \ default_user=worker \ allowed_users=worker, worker2 \ cidr_list=10.10.10.10/32
Далее осталось создать policy к нашей роли для генерации ключей и сгенерировать access-token, привязанный к этой policy.
tee test.hcl <<EOF path "ssh/creds/otp_role" { capabilities = ["update"] } EOF
vault policy write otp-polcy ./test.hcl
Создаём token и сохраняем его, потому что потом запросить его у vault не удастся.
vault token create -policy=otp-polcy Key Value --- ----- token hvs.CAESIG1_CrngaECzf6yvTDBgUZz2Lt-mYfdZXogrsiV0ulH1Gh4KHGh2cy4bPmFmN24xNVM5cnBqbFNLTUdpd1JDcTM token_accessor n76E8Bc8P9SyPLpVZa2EoWGq token_duration 768h token_renewable true token_policies ["default" "otp-polcy"] identity_policies [] policies ["default" "otp-polcy"]
Манипуляции внутри нужной машины
Скачиваем последнюю версию vaul-ssh-helper с этой ссылки, указанной в этом репозитории.
wget https://releases.hashicorp.com/vault-ssh-helper/0.2.1/vault-ssh-helper_0.2.1_linux_amd64.zip
Распаковываем в директорию /usr/local/bin.
unzip -q vault-ssh-helper_0.2.1_linux_amd64.zip -d /usr/local/bin
Указываем владельцем root'а и устанавливаем права доступа 0755 (rwxr-xr-x).
sudo chown root:root /usr/local/bin/vault-ssh-helper
sudo chmod 0755 /usr/local/bin/vault-ssh-helper
Создаём файл с конфигурацией vault-ssh-helper в директории /etc/vault-ssh-helper.d.
sudo mkdir /etc/vault-ssh-helper.d
sudo tee /etc/vault-ssh-helper.d/config.hcl <<EOF vault_addr = "VAULT_ADDR" tls_skip_verify = false ca_path = "CA_CRT_PATH" ssh_mount_point = "ssh" allowed_roles = "*" EOF
Меняем конфигурация для PAM sshd (/etc/pam.d/sshd).
# на всякий случай бэкапим её sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.orig
Комментируем строчку include common-auth и добавляем следующие две: в них для vault-ssh-helper указываем конфиг helper'а и файл, куда будут выводиться логи.
auth requisite pam_exec.so quiet expose_authtok log=/var/log/vault-ssh.log /usr/local/bin/vault-ssh-helper -config=/etc/vault-ssh-helper.d/config.hcl auth optional pam_unix.so not_set_pass use_first_pass nodelay
Меняем конфигурацию sshd (/etc/ssh/sshd_config).
# на всякий случай бэкапим её sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.orig
Добавляем вот такие строчки (предварительно проверьте файл, возможно они уже вставлены).
KbdInteractiveAuthentication yes UsePAM yes PasswordAuthentication no
Старые версии Ubuntu используют
ChallengeResponseAuthenticationвместоKbdInteractiveAuthentication.
Так как вы отключаете аутентификацию по паролю, убедитесь, что возможны другие способы доступа к серверу. Например доступ по ssh ключу у root'а.
Перезапускаем sshd.
sudo systemctl restart sshd
Проверка
Авторизуемся в vault с токеном, полученным выше.
vault login $TOKEN
Генерируем OTP ключ для нужного ip-адреса (юзера не указываем – используется дефолтный).
vault write ssh/creds/otp_key_role ip=$IP_ADDRESS Key Value --- ----- lease_id ssh/creds/otp_key_role/yZaiE4bRiVUVvc6LetQDtDmS lease_duration 768h lease_renewable false ip 10.10.10.10 key edcf6837-902f-43c6-9e54-c8c26ab80ff3 key_type otp port 22 username username
Подключаемся к серверу с полученным ключём.
ssh username@10.10.10.10
Нюансы
Чтобы лучше понимать некоторые пункты
Роль в ssh secret engine – это конфигурация для генерации SSH-учетных данных. В рамках OTP она включает в себя такие параметры, как, например: списки разрешённых ip-адресов и пользователей, а также дефолтное имя пользователя, для которого сгенерируется OTP в случае, если имя пользователя не будет передано.Lease в Vault – это метаданные, которые содержат информацию о времени действия, возможности обновления и т.д., связанных с каждым динамическим секретом и токеном.
#1 Генерировать на одного юзера можно сколько угодно ключей
Имея токен с доступом к нужной роли ssh engine, можно создавать сколько угодно одноразовых ключей для всех пользователей и всех машин, которые указаны в роли. И живут они около месяца. Поэтому рекомендуется ограничивать конфигурацию роли одним пользователем на одной машине.
#2 Сгенерированные ключи останутся рабочими после удаления роли
Пока валиден lease созданного ключа, не важно, существует роль, которой его выдали, или нет – ключ останется валидным.
#3 Узнать, сколько сейчас неиспользованных ключей для пользователя, невозможно
В принципе узнать, сколько было выдано на роль ключей, можно, посмотрев, сколько у этой роли lease'ов, привязанных к ключам.
curl -s -X LIST -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_ADDR}/v1/sys/leases/lookup/ssh/creds/$ROLE_NAME
Но понять, какой lease указывает на валидный ключ, а какой нет, вы не сможете.
#4 Инвалидировать ключ можно только отозвав привязанный к нему lease
Так как ключи живут даже после удаления роли, единственный способ их убить, это отозвать lease.
Можно точечно:
vault lease revoke ssh/creds/otp-role/lease-id
А можно отрубить сразу lease'ы всех ключей роли:
vault lease revoke -prefix ssh/creds/otp-role
#5 По дефолту логи использования access-token'ов в HashiCorp Vault не ведутся
Чтобы узнать какие access-token'ы, когда и для чего использовались, нужно включить запись логов обращения к API.
vault audit enable file file_path=ПУТЬ_ДО_ФАЙЛА_КУДА_БУДУТ_СОХРАНЯТЬСЯ_ЛОГИ
Файл с логами будет очень жирным.
Все token'ы и их accessor'ы в логах будут захешированы. Чтобы как-то найти строчки использования нужного вам token'а, вам придется сначала прогнать его через ту же хеш-функцию, которую использует audit. И дальше по полученному хешу искать в лог-файле.
vault write sys/audit-hash/file input="<TOKEN ACCESSOR>"
Автоматизация получение OTP
Автоматизировать этот процесс очень легко благодаря Vault API и гибкой настройке прав доступа через ACL. По сути просто создаёте пользователя или сразу token с необходимой policy, и можете использовать нужные вам роли для генерации OTP ключей.
Пример с Ansible
Самый, наверное, простой способ – это создать Ansible Playbook, запрашивающий OTP у vault напрямую модулем community.hashi_vault.vault_write.
- name: Play hosts: localhost gather_facts: false tasks: - name: Get OTP community.hashi_vault.vault_write: auth_method: token url: https://vault.service token: YOUR_TOKEN path: ssh/creds/otp_role data: ip: HOST_IP register: ssh - name: Setting ssh vars ansible.builtin.set_fact: hashi_host_ip: '{{ ssh.data.data.ip }}' hashi_host_username: '{{ ssh.data.data.username }}' hashi_host_port: '{{ ssh.data.data.port }}' hashi_host_key: '{{ ssh.data.data.key }}' - name: DBG ansible.builtin.debug: msg: - 'ip: {{ hashi_host_ip }}' - 'port: {{ hashi_host_port }}' - 'username: {{ hashi_host_username }}' - 'password: {{ hashi_host_key }}'
Пример с использованием API запросов
curl --location 'https://vault.service/v1/ssh/creds/otp_role' \ --header 'X-Vault-Token: TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "ip": "10.10.10.10" }' | jq '.data'
Спасибо за чтение! :)
