Найти тему
WeslyG

Я системный инженер, и я не могу подключиться по ssh

Оглавление

Привет, я системный инженер, и я не могу подключиться по ssh

И да, я знаю, как пишется команда подключения

ssh root@my-super-vm

однако моя проблема в том, что делать это нужно быстро, часто, и в очень разные окружения.

И с окружениями основная беда. Вот так выглядит типичное подключение на боевую машину. (название изменено, но количество символов совпадает)

ssh my-name@vm-myservice.production.mysite-legasy.ru

вводить каждый раз этот длиннющий префикс просто невыносимо, так же как и держать его в истории и подтягивать его по ctrl + R.

К тому же, нужно помнить какая машина в проде, какая в стейдже, а какая и вообще у других команд за proxy сервером и приписывать разные префиксы.

Но подожди!

-2

Знающие ребята скажут, подожди, зачем приписывать префиксы, есть же

/etc/resolv.conf

Добавим в него нужные префиксы и ходи спокойно через hostname, а весь resolve доменных имен будет на dns.

И это отличный вариант если бы не прокси сервера.

Все дело в том, что для доступа на разные площадки используются прокси сервера, и пройти на машину можно лишь через другие машины.

Поэтому на прямую имена не пингуются, даже если прописать их префиксы в конфиг резолвера.

Вот и получается, выясни куда хочешь идти, подумай какой префикс, найди его в истории. припиши нужную машину, и вперед!

Меня это совсем не устроило, и я решил копать конфиги.

Немного про конфиги

-3

Первым же делом, идем в ssh конфиги и прописываем все что нам нужно, для всех площадок

vi ~/.ssh/config

Например зададим имя пользователя для разных площадок, включим форвард агента, для прокидывания наших ssh ключей на машины. Установим интервал активной сессии, что бы не рвало соединение, и не дисконектило от сервера за бездействие.

И добавим конфигурации для настройки proxy серверов для конкретных префиксов.

Host *.production
User my-prod-akk
ProxyCommand  ssh %r@my-production-gateway.com -W %h:%p
Host *.develop
User my-dev-akk
StrictHostKeyChecking no
ProxyCommand  ssh %r@my-develop-gateway.com -W %h:%p
Host *
User root
ForwardAgent  yes
ServerAliveInterval 120

Ну и если вы часто ходите на новые машины и вечно встречаете приглашение записать новую машину себе в know hosts, и это ваша локальная сеть которой вы доверяете, то можно включить эту опцию.

StrictHostKeyChecking no

что позволит нам не терять время на добавление нового хоста в известные. Эта опция не особо безопасна, однако в локальной сети, может быть полезна, особенно когда машин много и меняются они часто.

Этого мало.

Это замечательно снизило нам нагрузку, теперь не надо париться за рвущиеся сессии, прокидывание ssh ключей на машины, а так же приписки username при подключении, это здорово.

Но главная проблема еще впереди, мы все еще не можем заходить на одну машину не вводя полный путь.

Пишем велосипед!

-4

lvl 1 - Решаем проблемы

Начнем с решения проблемы - самое простое и быстрое решение, добавить в .bashrc такую функцию (super ssh)

sssh() {
ssh $1 2> /dev/null
if [ $? -ne 0 ]; then
ssh $1.my-production.hostname.ru
fi
}

Обновим .bashrc

source ~/.bashrc

И можно пробовать

sssh my-secret.vm

Все что делает функция, это пытается подключиться во внешний мир, если результат выполнения команды не 0 т.е ошибка, то она подставит к нему адрес префикс. И тут мы намеренно отбрасываем stderr из первого подключения. что бы не видеть ошибки о подключении постоянно. Это можно отключить удалив из скрипта вот эту часть:

2> /dev/null

Но что если пойти дальше?

Сделаем единый скрипт который будет обрабатывать всю нашу работу с ssh на всех возможных площадках с нативным синтаксисом и кучей дополнительных возможностей.

lvl 2 - на старт

-5

Добавим статику, добавим SSH туннели, чуть прокачаем сам ssh.

Статика - грубо говоря алиас. Что бы на определенное слово, мы подключались к определенным стендам. Нужна в основном для тех самых proxy серверов.

Туннели - возможность пробросить с любой машины к себе на компьютер порт, или несколько портов. Что бывает невероятно полезно.

Со статикой все банально, и много нам ее не понадобиться.

#!/bin/bash
if [ $1 == "gate" ]; then
ssh username@my-production.hostname.ru
fi

ssh $1 2> /dev/null
if [ $? -ne 0 ]; then
ssh -t $1.my-production-prefix.ru 'sudo -i'
fi

Немного о флаге -t, он позволяет нам выполнить любую команду сразу после подключения к серверу, а подключаемся мы под обычным пользователем. Но как правило идем решать проблемы, а значит рут доступ нам необходим, а значит, первое что мы сделаем это пропишем

sudo -i

Автоматизируем это внеся команду на выполнение.

Теперь нам нужно сохранять это в отдельный файл, я назову его sssh (super ssh)

и закину путь до него в PATH.

А так же не забуду дать права на исполнение

chmod +x sssh

В результате теперь я могу подключиться к моему серверу просто введя

sssh machine-name

и увидеть приветствие рута.

Настроим SSH туннели.

А заодно посмотрим что будет если Proxy сервер у нас не один, а их несколько.

Как нам известно, синтаксис для создания ssh туннелей такой:

ssh -N my-machine -L 3000:localhsot:3000 -L 9200:localhost:9200

Что позволит нам прокинуть порты ssh 3000 и 9200 с my-machine на localhost.

Портов может быть много, для примера я показал 2.

Теперь посмотрим как это можно оформить, с несколькими прокси серверами.

if [ $1 == "-N" ]; then
list=$(for ((i = 3; i <=$#; i++)); do
echo "${!i} "
done)
ssh -N $2.my-production-prefix.ru $list 2> /dev/null
if [ $? -ne 0 ]; then
ssh -N $2.my-test-prefix.ru $list 2> /dev/null
if [ $? -ne 0 ]; then
ssg 0B $2 $list
fi
fi
fi

Таким образом, мы смотрим что команда началась у нас с флага -N что означает что мы хотим использовать туннель. Подставляем в порядке приоритетности, сначала боевую, затем тестовую площадку, затем внешний мир.

Игнорируя stderr что бы глаза от него не болели.

Теперь команда:

sssh -N my-product-vm -L 3000:localhost:3000

прокинет мне туннель из production сети.

и точно такая же по длине команда, сделает это из тестовой и из внешнего мира.

Не забывайте, что для пробрасывания порта ниже 1000 порта, нужны root права!

Lvl 3 - время гигов.

-6

SCP - Secure Copy утилита для копирования файлов, через ssh.

Синтаксис довольно прост.

scp index.html my-production-vm:/tmp

Будет работать точно так же как ssh - есть ключ и права, скопирует не спрашивая, а нет - заставит вводить пароль.

К слову она работает и обратно:

scp my-production-vm:/distr/index.html /tmp

Это мы и будем оборачивать.

Добавим этот модуль в наш скрипт:

if [ -f $1 ]; then
vm=$(echo $2 | awk -F ":" '{ print $1}')
file=$(echo $2 | awk -F ":" '{ print $2}')
scp $1 $vm.my-production-prefix.ru:$file 2> /dev/null
if [ $? -ne 0 ]; then
scp $1 $vm.my-develop-prefix.ru:$file 2> /dev/null
if [ $? -ne 0 ]; then
scp $1 $vm:$file
fi
fi
else
vm=$(echo $1 | awk -F ":" '{ print $1}')
file=$(echo $1 | awk -F ":" '{ print $2}')
scp $vm.my-production-prefix.ru:$file $2 2> /dev/null
if [ $? -ne 0 ]; then
scp $vm.my-develop-prefix.ru:$file $2 2> /dev/null
if [ $? -ne 0 ]; then
scp $vm:$file $2
fi
fi
fi

И что мы получаем.

Теперь если первым аргументом будет передан файл на локальной машине, то из второго аргумента скрипт сам поймет, где машина а где путь (по разделителю :) и подставит все в нужные места, и так же обратно.

lvl 4 - Взлетаем

-7

Мы уже говорили тут о файле ~/.bashrc этот файл считывается при старте ssh соединения, и в него подгружаются все пользовательские конфигурации, мы добавляли в него функцию, однако есть еще более нужная вещь в этом файле это Alias. короткие сокращения на которые можно задать длинные команды. И это просто потрясающе!

Возьмем пример, я на своих машинах очень часто работаю с системой сбора и поиска данных - Elasticsearch поэтому мне часто приходится пользоваться API elastic. Которое я конечно же выучил наизусть, но в нем есть много методов, какие то я не помню, какие то постоянно но их долго писать.
А что если бы я создал специальный файл .bashrc который бы загружался у меня на каждой машине? Что бы при заходе на нее у меня был бы свой набор алиасов?

Ну например такой.

#elk
alias health='curl localhost:9200/_cluster/health?pretty'
alias shards='curl localhost:9200/_cat/shards'
alias indices='curl localhost:9200/_cat/indices'
alias nodes='curl localhost:9200/_cat/nodes'
alias explain='curl localhost:9200/_cluster/allocation/explain?pretty'
alias settings='curl localhost:9200/_cluster/settings?pretty'
alias retry_failed='curl -XPOST localhost:9200/_cluster/reroute?retry_failed'

Вот список самых частых запросов к апи которые мне нужны, и мне приходится так же их искать или писать руками, это долго, неудобно, да и забыть команды можно, алиас запомнить намного проще.

Но есть ограничения, нельзя перезаписывать системный набор .bashrc во первых к нему привыкли другие пользователи, и я могу что нибудь сломать, во вторых кто то может не знать о таком алиасе и очень удивиться неожиданному появлению странных команд. Ну и естественно это должно пробрасываться только на меня одного, и быть доступным только на время сессии, после чего удаляться.

По итогу получился вот такой страшный скрипт, я постараюсь его объяснить.

scp ~/.bashrc_remoute $1.my-production-prefix:/tmp/ &> /dev/null
ssh $1.my-production-prefix -t "sudo -i \
\cp -r /root/.bashrc /tmp/.bashrc_temp \
&& cat /tmp/.bashrc_temp >> /tmp/.bashrc_remoute \
&& sudo -i bash --rcfile /tmp/.bashrc_remoute; \
sudo -i rm -rf /tmp/.bashrc*" 2> /dev/null
if [ $? -ne 0 ]; then
scp ~/.bashrc_remoute $1.my-production-prefix:/tmp/ &>/dev/null
ssh $1.my-production-prefix -t "sudo -i \
\cp -r /root/.bashrc /tmp/.bashrc_temp \
&& cat /tmp/.bashrc_temp >> /tmp/.bashrc_remoute \
&& sudo -i bash --rcfile /tmp/.bashrc_remoute; \
sudo -i rm -rf /tmp/.bashrc*" 2> /dev/null
if [ $? -ne 0 ]; then
ssh $1
fi
fi

Итого: в начале мы через scp закидываем заранее подготовленный файл с алиасами. (называться он может произвольно)

Затем подключаемся по ssh и раз уж мы умеем выполнять команду при запуске, то давайте просто сделаем ее массивной, поднимем права доступа до рута, чтобы дотянуться до оригинального .bashrc (в нем содержатся действительно полезные дефолтные вещи, не стоит полностью перезаписывать все на свое)

Скопируем его в /tmp папку, где и лежит наш принесенный с нашей машины файл, а далее, поскольку прав на запись в оригинальный .bashrc у нас нет, мы просто считаем его и запишем в скопированный.

Ну а дальше подгружаем наш скопированный как основной файл входа в систему, подняв перед этим права, и оставляем колбек в виде "удали все эти файлы" когда моя сессия закончится. И даже если сессия разорвется не запланировано, и эта команда не исполнится, в следующий раз эти файлы перезапишутся. И мы снова будем видеть актуальное состояние наших алиасов.

Практически мы только дописали новый алиас, и в следующее подключение можем использовать его. Это приятно, нет зависимости от ansible ролей, нет зависимости от других членов команды, их настроек и тд. Мы просто носим все свое с собой.

К тому же ничего не ограничивает нас написать разный набор алиасов для разных подсетей, если это необходимо, однако мне проще написать все в один файл, а так же никто не запрещает все это проворачивать только на одной из площадок, а на других оставить все как есть, что я и сделал тут с внешним миром.

И я еще немного поработал над скриптом, добавил поддержку портов, больше обработок, написал небольшой хелпер, и обработчик на пустой ввод.
все это завернул в простой и лаконичный alias = s

И теперь на этот алиас я могу делать все что угодно

s -N my-prod -L 3000:localhost:3000

или так

s mytest.txt my-prod:/tmp

или просто так

s my-dev

И все из единой точки.

-8

Итоги:

Мы получили единую точку входа для всего и теперь мы имеем.

ssh

  • работу с несколькими окружениями и внешним миром
  • нативный синтаксис ssh
  • поддержка портов
  • поддержка кастомного имени пользователя
  • статические алиасы
  • ssh туннели с поддержкой любого количества портов и нативным синтаксисом.
  • проброс своего окружения(алиасов) на машину, не меняющий логики поведения других пользователей машины, и не зависящий не от кого.

scp

  • нативный синтаксис SCP
  • поддержка портов
  • поддержка нескольких прокси серверов, и внешнего мира

helper

  • если забыли команды
  • если не ввели аргументы.

Минусы:

-9

Надо понимать, что за это нам пришлось многим заплатить, скажем в этом скрипте нет обработки совершенно всех ошибок, потому что мы намеренно игнорируем некоторые stderr что бы не видеть их каждый раз.

Так же есть ограничения, что в scp порт пишется в начале а в ssh в конце (иначе понять, что из них что было бы не просто) вся эта информация указана в хелпере, и если что пошло не так, всегда можно воспользоваться ванильным ssh.

Мы же осознаем, что большая часть наших подключений будут универсально простыми, просто в разные окружения. Поэтому ожидать сюрпризов можно в крайних случаях.

Так же, если машина одинаково называется в двух окружениях, у нас проблемы, мы подключимся к той, что будет в приоритете. Вообще я не встречал еще ни разу чтобы машины одинаково назывались в разных окружениях, но я совершенно этому не удивлюсь.

На этот случай так же есть ванильный SSH.

Стало ли это все работать жутко долго?

Нет)

Но конечно при подключении ты чувствуешь "некоторую задержку" опять же, я говорю о себе, и своей "локальной сети" у вас все может быть не так радостно.

Однако насколько же ускорилось наше поведение

Теперь вводя команду копирования, проброса порта, или подключения, мне даже не надо знать, где находится эта машина, мой велосипед все сделает за меня.

Бонус за прочтение

-10

И если ты читаешь это, значит тебе очень интересно, а мне это приятно, можешь глянуть, что вышло в итоге. И попробовать мой скрипт самому.

https://github.com/WeslyG/super-ssh

Если дочитал то тебе точно понравится мой канал в телеграмме)

Присоединяйся! https://t.me/weslyg_channel