Налаштування Jump Host для SSH з’єднань (Частина 6)

22 березня 2024 3 хвилин Автор: Lady Liberty

Пояснюється, як здійснити підключення до мережі через SSH, використовуючи так званий “jump host”. Йдеться про налаштування безпечного тунелю між домашньою та робочою мережами з використанням SSH, що дозволяє здійснити підключення до серверів у внутрішній мережі через проміжний хост (jump host). Описано кілька способів підключення, включно з налаштуванням SSH-клієнта для спрощення процесу підключення через jump host та використання SSH-ключів для автоматизації аутентифікації.

Як потрапити до мережі через SSH

Минулого разу ми нарешті налаштували зв’язок між мережею GNS і нашою домашньою мережею. Зв’язує їх router1, у якого на одному з інтерфейсів є адреса домашньої мережі. Так само в реальній інфраструктурі у вас є роутер, який має на одному з інтерфейсів публічну адресу, видану провайдером.

У реальній мережі ви не можете так просто з дому підключитися до робочої мережі:

ping 10.0.2.101
ip ro sh

Як правило, для дистанційної роботи співробітників організують VPN сервер, за допомогою якого створюється безпечний тунель з однієї мережі в іншу. Майже всі сучасні роутери і UTM рішення мають такий функціонал, тому ми теж підніматимемо свій впн сервер, але не сьогодні. Навіть без VPN ми можемо організувати безпечний тунель і для цього достатньо одного ssh. Для початку уявімо, що нам треба підключитися по ssh до серверів.

Найпримітивніший спосіб – підключитися по ssh до роутера:

а далі з нього підключатися до інших машинок, наприклад, до сервера1:

В принципі і так зійде. Але якщо у нас безліч машин і нам постійно потрібно заходити сюди-туди, це стає трохи незручно.

Ми використовуємо роутер як проміжний хост для підключення. Такі проміжні хости, через які можна потрапити з однієї мережі до іншої, при цьому без маршрутизації – називаються jump host-ами. Не звертайте уваги, що ми використовуємо роутер як джамп хост, насправді це може бути будь-яка машинка, яка має доступ в обидві мережі.

Так ось, ssh клієнт може спростити підключення через джамп хости. Досить відразу вказати за допомогою ключа -J, що такий сервер є джамп хостом, а ми хочемо підключитися туди-то:

На тлі відбувається той самий процес, що й минулого разу – спочатку ми заходимо на роутер, а вже потім на сервер1. Ми все одно маємо вказувати паролі від обох хостів. Але тепер нам це виглядає не як два кроки, а як один. Так, команда вийшла справжнішою, але це справа поправна.

Ми можемо в конфігах свого ssh клієнта ~/.ssh/configвказати обидва хости. І сказати, що для підключення до server1 потрібно використовувати router1 як ProxyJump:

Host router1
  Hostname 192.168.31.252
  User root

Host server1
  Hostname 10.0.2.101
  User root
  ProxyJump router1

І тепер для підключення достатньо вказати ім’я цільового хоста, яке ми прописали у конфізі:

ssh server1

Ми, як і раніше, повинні вводити паролі як від джамп хоста, так і цільового. Але навіть ця справа можна виправити. Для цього нам потрібні ключі ssh.

Для початку згенеруємо ключі.

ssh-keygen

Раз вже взялися будувати інфраструктуру, давайте робити це правильно.

Спочатку питання – чи повинні ми робити для кожного сервера окремий ключ? Чи використовувати один ключ на всіх серверах? Незважаючи на всю очевидність, питання досить складне. З одного боку, мати для кожного сервера окремий ключ здається безпечнішим – втративши один приватний ключ ми не наражемо на небезпеку інші. З іншого боку – яка ймовірність, що ми втратимо лише один ключ, а не всі одразу?

 Вони ж лежать в одній директорії на одному комп’ютері адміністратора. Плюс ще кудись бекапаться. Через те, що підібрати приватний ключ практично нереально, хакерам набагато легше буде вкрасти ключі з комп’ютера адміну. Від цього ми можемо захиститися, поставивши пароль на ключі.

 Щоправда, зломщик може генерацією паролів підібрати потрібний. І тут, якщо у нас один пароль на всі ключі – то знову ж таки він отримає доступ на всі сервери. А ось якщо ми поставимо різні паролі на різні ключі – то злом усіх ключів займе набагато більше часу. Але, знову ж таки, і це теоретично можливо. Тому, в ідеальному випадку, ми повинні мати різні ключі для різних хостів з різними паролями і ми їх повинні періодично змінювати. Коли інфраструктура не маленька, то морочитися з ключами забирає дуже багато часу. Можливо, якщо у нас дуже критичні дані і нас постійно хтось намагається зламати – цього варте.

Але в спокійніших умовах можна піти на компроміс. Скажімо, ділити хости по групах і на кожну групу видавати одну пару ключів із окремим паролем. Ну і звичайно, періодично змінювати ключі.

І слідуючи нашому висновку, згенеруємо окремий ключ для джамп хоста:

ssh-keygen

Назвемо ключі .ssh/jumphost та задамо пароль для приватного ключа.

Після генерації закинемо ключ на джамп хост за допомогою команди ssh-copy-id:

ssh-copy-id -i .ssh/jumphost.pub router1

І після введення пароля ключ додався до сервера. Тепер, при підключенні до сервера, треба вказувати ключ, за допомогою якого ми хочемо зайти на роутер1:

ssh -i .ssh/jumphost router1

При підключенні просять ввести пароль від ключа, який ми створили. А після введення пароля від ключа, на сервер ми потрапляємо без пароля.

Тепер згенеруємо ключі для серверів:

ssh-keygen

Вказуємо для них окремий файл та пароль.

Перш ніж закинути ключ на сервер1, вкажемо в конфіг файлі ~/.ssh/config, що для джампхосту потрібен окремий ключ:

...
IdentityFile ~/.ssh/jumphost
...

Тепер спробуємо закинути ключ на сервер1:

ssh-copy-id -i .ssh/servers.pub server1

ssh у нас попросить кілька разів ввести пароль від ключа для джампхоста – один раз він спробує зайти за допомогою ключа, чи мало, раптом він уже встановлений на сервері, потім він спробує додати ключ, для цього додатково попросить пароль від самого сервера. Потім ми побачимо інформацію про те, що ключ успішно доданий. Тепер можемо спробувати зайти за допомогою ключа:

ssh -i .ssh/servers server1

Для цього треба буде ввести паролі від ключів джампхосту та серверів. Після цього на сам сервер ми зайдемо без пароля.

Також вкажемо ключ у конфіг файлі ~/.ssh/config, щоб не вказувати ключі при підключенні:

...
IdentityFile ~/.ssh/servers
...

На цей раз при підключенні ми знову ж таки не вказуємо ніякі опції, а просто вводимо паролі від ключів. Але вводити ці паролі при кожному підключенні може бути незручно, особливо коли у нас безліч серверів і ми постійно підключаємося туди-сюди. І начебто не хочеться постійно вводити паролі, але вони повинні бути на ключах. Для цього є просте рішення – ssh-agent. Коротко – ви запускаєте процес ssh-agent-а та додаєте до нього ключі. Поки процес працює – вам не потрібно буде вводити паролі від ключів. Умовно, починаєте працювати – запускаєте ssh-agent і додаєте ключі, вводячи паролі від ключів. Далі спокійно працюєте без паролів. Припиняєте працювати – йдете на обід чи перерву – вбиваєте процес.

Для запуску агента використовуєте команду:

ssh-agent

При цьому команда показує пару команд, за допомогою яких ви повинні створити змінні оточення. Без цих команд змінні не пропишуться:

env | grep SSH

І тоді ви не зможете працювати із агентом. ssh-агент сам не може прописати ці змінні. Тому потрібно вручну скопіювати ці команди та виконати:

SSH_AUTH_SOCK=/tmp/ssh-XXXXXXuoOHmy/agent.159457; export SSH_AUTH_SOCK;
SSH_AGENT_PID=159458; export SSH_AGENT_PID;
env | grep SSH

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

pkill ssh-agent

А потім запустимо новий, але через команду eval:

eval $(ssh-agent)

Вона одразу додасть змінні:

env | grep SSH

Тепер ми можемо додати ключі до агента за допомогою команди ssh-add:

ssh-add .ssh/jumphost
ssh-add .ssh/servers

Після чого, при спробі логіну на сервер або джампхост, жодного пароля не потрібно:

ssh server1
ssh router1

І, з одного боку, у нас ключі захищені паролем, а з іншого – тепер це також зручно, як без пароля, хіба перед роботою треба прописати пару команд.

Давайте постараємося зробити нашу схему трохи реальнішою. Зазвичай у компаніях на роутері не стоїть лінукс, або там такий урізаний лінукс, що мало що зробиш. Тому сам роутер використовувати як джамп хоста не вийде. Нехай краще буде сервер1. Але як, запитаєте ви, він недоступний з інтернету? А це справа поправна. Коли у нас якийсь хост знаходиться в ізольованій мережі і ми не маємо до нього прямого доступу, ми все ж таки можемо до нього потрапити використовуючи NAT.

Минулого разу ми розбирали source nat, коли наші пакети на виході з мережі підміняли Source IP і Source Port, щоб пакети могли повернутися назад у мережу з приватними адресами.

Зараз нам потрібно, щоб підмінялися destination адреси, оскільки той, хто хоче підключитися, не знає чи може підключитися до внутрішніх адресам, нам потрібно перенаправляти пакети на внутрішню адресу. Тому за допомогою NAT ми можемо замінити destination адресу і порт. Це називається dnat або прокидання портів.

Отже, хочемо, щоб вказуючи адресу роутера1, ми підключалися по ssh до server1. При цьому через те, що наш роутер, умовно, знаходиться в інтернеті, 22 порт використовувати не варто – в інтернеті купа ботів, які намагатимуться зламати наш сервер. Тому виберемо якийсь нестандартний порт для ssh.

Давайте перенаправляти все, що приходить на зовнішню адресу роутера на порт 17777 на сервер 1 по порту 22.

Для цього заходимо на роутер:

ssh router1

Інтерфейс із зовнішньою адресою у нас знаходиться в зоні external на файрволі:

firewall-cmd --list-all --zone=external

Тут ми повинні прокинути порт.

Команда вийде трохи довга, але нічого складного в ній немає:

firewall-cmd --zone=external --add-forward-port=port=17777:proto=tcp:toaddr=10.0.2.101:toport=22 --permanent

Спочатку ми вказуємо зону, у якій хочемо додати правило. Оскільки інтерфейс перебуває у зоні external, її вказуємо. Далі використовуємо ключ --add-forward-port, щоб додати правило для прокидання порту. Після одно пишемо, який порт хочемо прокинути – port=17777, який протокол – proto=tcp, який адресу – toaddr=10.0.2.101і який порт – toport=22. Всі ці значення поділяються двокрапками. Ну і в кінці не забуваємо вказати --permanent, щоб наші зміни збереглися в налаштуваннях.

Після чого перевантажуємо правила:

firewall-cmd --reload

І перевіряємо результат:

firewall-cmd --list-all --zone=external

Як бачите, в зоні external з’явилося правило під forward-ports.

Так як тепер наш сервер1 доступний безпосередньо з інтернету, то в налаштуваннях ssh – ~/.ssh/configприбираємо рядок ProxyJump, а замість неї прописуємо зовнішню адресу роутера і порт:

...
Hostname 192.168.31.252
Port 17777
...

При першому підключенні:

ssh server1

ми побачимо повідомлення, що наш комп’ютер дізнався цей хост і раніше він мав адресу 10.0.2.101, зараз ми підключаємося до нього за іншою адресою. Пишемо yes та підключаємося без проблем.

Тепер налаштуємо роутер. Зараз, умовно, будь-хто може до нього підключитися по ssh:

firewall-cmd --list-all --zone=external

Для більшої безпеки прибираємо цю можливість:

firewall-cmd --remove-service=ssh --zone=external --permanent
firewall-cmd --reload

Тепер у нас склалася така ситуація, що безпосередньо на роутер ми потрапити не зможемо, проте можемо через сервер1. Тобто. тепер ~/.ssh/configвказуємо, що для підключення до роутера потрібно використовувати server1 як JumpHost-а. Ну і пишемо внутрішню адресу роутера:

...
ProxyJump server1
Hostname 10.0.2.1
...

Перевіряємо підключення:

ssh router1

все працює.

І наше з’єднання до роутера тепер виглядає так. Спочатку ми підключаємося до зовнішньої адреси роутера портом 17777, звідки файрвол перекидає наші пакети на сервер1 на порт 22. Там ми логінимся по ssh і відразу підключаємося звідти по ssh до внутрішньої адреси роутера. І все це однією командою:

ssh router1

У вас може виникнути питання – чому б просто не прокидати порти на всі сервери? Навіщо потрібний jump host? Тут кілька причин:

  • по-перше, безпека. Коли у вас jump host, то зовні по ssh доступний лише один хост. А це означає, що всі спроби злому будуть спрямовані лише на нього. Поки його не зламають, всі інші хости будуть невидимі. По суті, jumphost це ваші двері до мережі. Легше захистити одні двері, ніж багато.

  • по-друге, зручність. Коли у вас 3 сервери – можна і прокиданням портів обійтися. А якщо у вас 300 серверів? Що, якщо вони постійно додаються та видаляються? Що, сидіти і постійно прокидати порти? А так, з одного jump-хоста можна потрапити на всі інші машини.

І так, як зробити наше підключення ще безпечнішим?

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

ssh-copy-id -i .ssh/jumphost.pub user@server1
ssh-copy-id -i .ssh/servers.pub user@router1

Оскільки тепер у нас джамп хостом є server1, то й ключ кидаємо відповідний. А для роутера кидаємо другий ключ. Потім треба буде підтерти старі ключі біля рута.

Підправляємо файл ssh config – ~/.ssh/config. По-перше, змінюємо користувача на user-а, щоб не доводилося при кожному підключенні вказувати його. По-друге – міняємо ключі.

...
User user
...

Після того, як переконаємось, що підключення працює:

ssh server1

найкраще дещо виправити на ssh-сервері, який доступний з інтернету:

sudo nano /etc/ssh/sshd_config

Знаходимо рядок PermitRootLogin і змінюємо значення на no:

... 
PermitRootLogin no
...

Ця опція дозволяє підключатися через ssh користувачем root. А оскільки всі знають, що на будь-якій лінукс системі є користувач рут, то і більшість ботів намагатимуться ломитися цим користувачем. Тому заборонивши логінуватися користувачем root – ми трохи ускладнюємо роботу ботам, тому що тепер їм доведеться вгадувати логін користувача, а не лише пароль.

А далі знаходимо рядок PasswordAuthentication і міняємо на no:

...
PasswordAuthentication no
...

Тим самим забороняючи логін за паролем. Тепер єдиним способом залогінитись буде використання ssh ключів, що відсіває всіх ботів, тому що навряд чи вони можуть дістати наші ssh ключі. Хоча теоретично, якісь боти можуть використовувати базу з якихось злитих приватних ключів, але це пряма рідкість. Цих налаштувань цілком вистачає більшості випадків, тому зберігаємо конфіг і виходимо.

Щоб застосувати налаштування, перезапускаємо sshd:

sudo systemctl restart sshd

Для тесту спробуємо залогінитись рутом:

ssh root@server1

Як бачите, відразу отримуємо помилку – permission denied.

А якщо спробувати зайти звичайним user-ом:

ssh server1

все вдасться.

Хоча використання нестандартного порту, заборона логіна руту та заборона логіна по паролях майже вирішують проблему з ботами, не можемо не розповісти про ще один механізм – fail2ban. У нашій ситуації він не особливо потрібен, але, якщо ви не можете з якихось причин поміняти стандартний порт, не можете заборонити логінитися за паролями – fail2ban вам знадобиться.

По суті, більшість ботів просто сканує публічні адреси по 22 і по інших портах, що часто використовуються, і намагаються підібрати пароль. Найчастіше пароль рута, але іноді й якісь інші стандартні логіни. Цей тип атаки називається brute-force. Легкі паролі можуть підібрати за пару хвилин, а складні – як пощастить. Ну і один із варіантів хоч якось стримувати це – блокувати адреси, які часто вводять неправильний пароль. Для цього, власне, і потрібен fail2ban. Він працює не лише з ssh, а й з іншими сервісами.

Щоб встановити fail2ban, спочатку потрібно підключити репозиторій epel:

sudo dnf install epel-release

А потім встановити пакет під назвою fail2ban:

sudo dnf install fail2ban -y

Після встановлення давайте стартанем сервіс і додамо його в автозавантаження:

sudo systemctl enable --now fail2ban

fail2ban дуже гнучка штука, яка дозволяє захистити від брутфорсу майже все, що завгодно. Для деяких сервісів, таких як ssh, є готові шаблони, а для самописних програм можна налаштувати самостійно. Дефолтні параметри fail2ban можуть відрізнятися залежно від дистрибутива. Давайте перевіримо, чи захищає fai2ban зараз щось? Багато речей робляться через утиліту fail2ban-client:

sudo fail2ban-client status

Наразі ми бачимо, що fail2ban нічого не захищає.

А значить нам треба створити файл /etc/fail2ban/jail.local.

sudo nano /etc/fail2ban/jail.local

Налаштування різних сервісів є у сусідньому файлі jail.conf. У jail.local ми лише перезаписуємо ці налаштування, а також вмикаємо їх.

Наприклад, щоб увімкнути налаштування sshd, додамо тут кілька рядків:

[sshd]

enabled = true

Після чого перезапустимо сервіс fail2ban:

sudo systemctl restart fail2ban

І ще раз перевіримо статус через fail2ban-client:

sudo fail2ban-client status
sudo fail2ban-client status sshd

Як бачите, тепер у нас є 1 jail – sshd. І поки що жодного забаненного IP. На поки що цього вистачить. Налаштувань багато, але детальніше з fail2ban попрацюємо якось у майбутньому. Якщо вам цікаво – в інтернеті безліч статей, та й у системі є документація.

Насамкінець покажемо, як з Windows підключатися. Будь-який ssh ​​клієнт підтримує роботу з jump хостами та ключами, і mobaxterm – не виняток.

Спочатку згенеруємо ключі для ssh. В інтерфейсі вибираємо Tools – MobaKeyGen.

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

Потім натискаємо Save public key, вибираємо папку для збереження та назву файлу. Також виготовляємо для приватного ключа.

Я зберіг на робочому столі, вам рекомендую тримати ключі так, щоб їх не могли легко знайти і вкрасти. Потім виділіть та скопіюйте публічний ключ із вікна генератора.

Після цього вставте ключ на сервері у домашній директорії користувача у файлі ~/.ssh/authorized_keysу новому рядку. До того ж переконайтеся, щоб один ключ був в один рядок. На першому рядку у мене ключ, згенерований на Linux, а на другому – те, що в mobaxterm. Після цього збережіть файл і вийдіть.

Так само скопіюйте ключ у файл authorized_keys на роутері. В ідеалі треба було зробити другий ключ, але ми впевнені, ви з цим справитеся.

Далі закриваємо вікно генератора, в mobaxterm вибираємо Session – SSH і заповнюємо поля:

  • remote host: зовнішня адреса роутера

  • ім’я користувача: користувач

  • порт: 17777

І внизу, у вкладці Advanced SSH settings ставимо галочку на Use private key і вибираємо приватний ключ, який ми зберегли до цього. Після цього натискаємо OK.

При підключенні mobaxterm запитає пароль для ключа. Вводимо його.

Після чого ви потрапите на сервер.

Тепер спробуємо підключитися до роутера, використовуючи сервер1 як джампхост. Починаємо також – Session – SSH та заповнюємо поля:

  • remote host: внутрішня адреса роутера

  • ім’я користувача: користувач

  • порт: 22

  • Advanced ssh settings – private key – вибираєте приватний ключ.

Далі відкриваєте Network Settings та натискаєте на SSH Gateway.

У вікні заповнюєте точно як заповнювали сервер1, тобто. зовнішню адресу, порт 17777 та приватний ключ від сервера. До речі, тут є кнопка – add another jump host. Теоретично, ваші цільові сервери можуть перебувати за кількома джамп-хостами. Але суть та сама. Натискаємо ОК, а потім знову ОК.

І ви одразу потрапите на роутер. Mobaxterm зберіг пароль від ключа і доки ви не закриєте його, вам не потрібно буде вводити паролі від ключів, як ми це робили з агентом ssh.

Невелике завдання вам – перейменуйте server1 в jumphost, організуйте окремі ключі для хоста user1, підніміть ще один сервер, закиньте відповідні ключі і підключіться на всі хости через jumphost.

Давайте підіб’ємо підсумки. Тепер у нас є доступ в нашу інфраструктуру ззовні по ssh за допомогою джамп хоста та прокидання портів. Ми також розібрали як трохи убезпечити наше підключення заборонивши логін по руту та без ключів.

Інші статті по темі
Знайшли помилку?
Якщо ви знайшли помилку, зробіть скріншот і надішліть його боту.