Организация доступа по SSH к виртуальным машинам Yandex Cloud
В чём, собственно, проблема зайти на виртуальную машину в облаке по SSH?
Во-первых чтобы зайти на виртуальную машину по SSH, ни о чём не думая, нужно держать на ней белый адрес IP, за который, по естественным в наше время причинам, облачные провайдеры просят отдельных денег. Которые при массовом использовании становятся заметными.
Во-вторых, если мы плюнули и потратились на внешние адреса для всех виртуалок, к которым нужен доступ по SSH, мы оказываемся должны ещё плюнуть и потратиться на организацию и поддержку файрволла для защиты сервисов, установленных на этих виртуалках.
Традиционно обозначенные проблемы решаются при помощи т.н. хоста-бастиона. Заводится хост, на который есть доступ из внешней сети, с которого, в свою очередь, есть доступ во внутреннюю сеть. Ко всем остальным хостам есть доступ только из внутренней сети. Оператор заходит по SSH на бастион и оттуда, уже имея доступ к внутренним адресам, на конечную машину.
В этом подходе вопросы может вызвать задача сопоставления имени внутренней машины её адресу IP. Традиционно заводится сервер DNS с поддержкой внутренней зоны. При разумном количестве статических адресов соответствие можно прописывать прямо при помощи ssh_config. Как мы суть облачные жители, для определения адресов воспользуемся API облака.
Предполагаем, что в каждом каталоге уже создана виртуальная машина под названием bastion-0 и на всех виртуальных машинах облака созданы учётные записи оператора с ключами ssh.
Заведём профиль для каждого каталога нашего облака и настроим SSH так, чтобы на виртуальную машину можно было попасть по псевдоадресу <instance-name>.<profile>.
Для настройки профиля можно запустить yc init и далее идти по диалогам:
> yc init Welcome! This command will take you through the configuration process. Pick desired action: [1] Re-initialize this profile 'default' with new settings [2] Create a new profile Please enter your numeric choice: 2 Enter profile name. Names start with a lower case letter and contain only lower case letters a-z, digits 0-9, and hyphens '-': prod Please go to https://oauth.yandex.ru/authorize?response_type=token&client_id=QMbwL7XPUHpyRdEAwr9o2RWVmuc4dyf7 in order to obtain OAuth token. Please enter OAuth token: zqHooQukL47xAGXL7od68jYnWLGVZOs4q_F0XQ4 You have one cloud available: 'mycloyd' (id = j1rhzWJevebiwI9x40fe). It is going to be used by default. Please choose folder to use: [1] prod (id = zss8DfD5hWRGvmSeZTcq) [2] dev (id = guLhrfu4uPfUtQeynPUp) Please enter your numeric choice: 1 Your current folder has been set to 'prod' (id = zss8DfD5hWRGvmSeZTcq). Do you want to configure a default Compute zone? [Y/n] n >
И так для каждого каталога. Если уже известен токен OAuth, возможно, удобнее будет просто отредактировать файл ~/.config/yandex-cloud/config.yaml:
current: dev profiles: prod: token: zqHooQukL47xAGXL7od68jYnWLGVZOs4q_F0XQ4 cloud-id: j1rhzWJevebiwI9x40fe folder-id: zss8DfD5hWRGvmSeZTcq dev: token: zqHooQukL47xAGXL7od68jYnWLGVZOs4q_F0XQ4 cloud-id: j1rhzWJevebiwI9x40fe folder-id: guLhrfu4uPfUtQeynPUp
Для профилей заведём в ~/.ssh/config записи:
Host *.prod ProxyCommand yc_ssh_proxy %h %p bastion-0 Host *.dev ProxyCommand yc_ssh_proxy %h %p bastion-0
Основной скрипт yc_ssh_proxy разместим по пути, перечисленному в $PATH (мне удобно держать в ~/bin/). Для работы скрипта понадобится python. Так получилось.
#!/bin/sh # # Usage: yc_ssh_proxy host.env port bastion # HOST=$(echo $1 | cut -d. -f1) PROFILE=$(echo $1 | cut -d. -f2) instance() { yc --profile $PROFILE compute instance get $1 --format json; } prop() { cat | python -c "import json;import sys;print(json.load(sys.stdin)['network_interfaces'][0]['primary_v4_address']$1)"; } ADDR=$(instance $HOST | prop '["address"]') BASTION=$(instance $3 | prop '["one_to_one_nat"]["address"]') exec ssh -W $ADDR:$2 -q $BASTION
Проверим доступность машин облака по ssh:
> yc --profile prod compute instance list +----------------------+---------------------------+---------------+---------+---------------+-------------+ | ID | NAME | ZONE ID | STATUS | EXTERNAL IP | INTERNAL IP | +----------------------+---------------------------+---------------+---------+---------------+-------------+ | yisv06r81u30jn925rhp | ut6ikj8lxmgmo4ikmymt-bl7c | ru-central1-a | RUNNING | | 10.2.0.23 | | fnyxiigmjy547fmr19l9 | bastion-0 | ru-central1-a | RUNNING | 152.2.34.233 | 10.4.0.36 | +----------------------+---------------------------+---------------+---------+---------------+-------------+ > ssh ut6ikj8lxmgmo4ikmymt-bl7c.prod Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-55-generic x86_64) . . . admin@ut6ikj8lxmgmo4ikmymt-bl7c:~$
Замечу, что при таком подходе статический адрес бастиону не требуется, он будет запрашиваться по Cloud API. Прерываемой же машину делать не стоит, замучаешься адреса подтверждать.
p.s.
Недавно снова нашёл потерянный репозиторий с плагином ansible inventory для облака Яндекс. На радостях добавлю пример настройки inventory для доступа к нодам кластера Managed Kubernetes.
Пока Яндекс определяется, как распространять плагин, положим его в ~/.ansible/plugins/inventory:
mkdir -p ~/.ansible/plugins/inventory curl https://raw.githubusercontent.com/st8f/community.general/yc_compute/plugins/inventory/yc_compute.py > ~/.ansible/plugins/inventory/yc_compute.py
Inventory запишем в файл yc_compute.yml:
plugin: community.general.yc_compute folders: - zss8DfD5hWRGvmSeZTcq filters: - labels['managed-kubernetes-cluster-id'] == 'catsv06r81u30jn925rh' auth_kind: oauth hostnames: - "{{ name }}.prod" keyed_groups: - key: labels['managed-kubernetes-node-group-id'] prefix: node_group
Вся соль здесь в ключе hostnames. Там каждой машине назначается имя, которое распознаёт настроенный нами клиент ssh. filters отбирает только машины заданного кластера (см. yc managed-kubernetes cluster list), а keyed_groups группирует их по ID групп инстансов. Связь проверим командой
> ansible all -i yc_compute.yml -m ping -o ut6ikj8lxmgmo4ikmymt-bl7c.prod | SUCCESS => {"changed": false,"ping": "pong"}