Ръководство за сигурен SSH: Отвъд паролите

Ръководство за сигурен SSH: Отвъд паролите

Съдържание

1. Защо SSH сигурността е критична

SSH (Secure Shell) е практически единственият начин, по който администрирам отдалечени Linux сървъри. Всяка грешка в конфигурацията му означава директен достъп до системата — файлове, бази данни, мрежова инфраструктура, всичко.

Статистиката говори сама за себе си: botnet-ите сканират целия IPv4 адресен диапазон непрекъснато. Ако имам публично достъпен SSH порт с парола, той ще бъде атакуван в рамките на минути след появата си в интернет. Не часове — минути.

Основни заплахи срещу SSH

  • Brute-force атаки — автоматизирани опити с речници от пароли
  • Credential stuffing — използване на изтекли пароли от други услуги
  • Man-in-the-Middle (MITM) — прихващане на сесии при неправилна верификация на host ключове
  • Компрометирани ключове — частни ключове, съхранени несигурно или без passphrase
  • Слаби алгоритми — стари cipher suite-ове с известни уязвимости

2. Password аутентикацията е мъртва — ето защо {#passwords}

Когато за пръв път настроих свой сървър преди години, използвах парола. Разбрах колко голяма е грешката, когато прегледах /var/log/auth.log и видях хиляди неуспешни опита за вход само за 24 часа — от адреси от цял свят.

Проблемите с паролите

Паролите могат да бъдат познати. Дори "сложна" парола като P@ssw0rd2024! се съдържа в повечето речникови файлове на атакуващите.

Паролите могат да изтекат. Дейта брийч в някой друг сайт, phishing, keylogger — всичко това компрометира паролата без дори да се знае.

Паролите не мащабират. При управление на 10+ сървъра, ротацията на пароли е кошмар. При SSH ключове — тривиална задача.

Паролите не оставят достатъчно одитна следа. SSH ключовете са уникални за всеки потребител/машина, което дава много по-детайлна видимост кой точно се е логнал.

Моето правило: Веднага след като добавя своя SSH ключ към нов сървър, правя PasswordAuthentication no в sshd_config. Без изключения.


3. SSH ключове: как работят и как да ги настроим правилно {#ssh-keys}

Как работи асиметричната криптография

SSH ключовата аутентикация се базира на двойка ключове:

  • Публичен ключ (~/.ssh/id_ed25519.pub) — поставя се на сървъра в ~/.ssh/authorized_keys. Може да се споделя свободно.
  • Частен ключ (~/.ssh/id_ed25519) — остава само при мен. Никога не напуска моята машина.

Сървърът изпраща предизвикателство (challenge), което може да бъде подписано само с частния ключ. Само притежателят на частния ключ може да докаже самоличността си — без да изпраща самия ключ по мрежата.

Генериране на ключове — правилният начин

Не всички алгоритми са равни. Ето йерархията ми от предпочитани:

# ✅ ПРЕПОРЪЧИТЕЛНО: Ed25519 — бърз, компактен, съвременен
ssh-keygen -t ed25519 -C "my-laptop-2024" -f ~/.ssh/id_ed25519

# ✅ АЛТЕРНАТИВА: ECDSA с 521-битов ключ
ssh-keygen -t ecdsa -b 521 -C "my-laptop-2024"

# ⚠️  RSA е все още приемлив, но само с 4096 бита
ssh-keygen -t rsa -b 4096 -C "my-laptop-2024"

# ❌ ЗАБРАНЕНО: DSA и RSA с по-малко от 2048 бита

Защо Ed25519? Предлага сигурност, еквивалентна на 3000-битов RSA ключ, при само 256-битов размер. Операциите са по-бързи и алгоритмът е устойчив срещу side-channel атаки.

Задължителна passphrase

# Генерирам ключ с passphrase — ВИНАГИ
ssh-keygen -t ed25519 -C "work-laptop" 
# > Enter passphrase: [въвеждам силна парола]

Passphrase-ът криптира частния ключ на диска. Дори при кражба на лаптопа, атакуващият не може да използва ключа без passphrase-а. Използвам ssh-agent, за да не го въвеждам всеки път:

# Стартиране на ssh-agent и добавяне на ключ
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Ключът се "помни" за текущата сесия

Копиране на публичния ключ към сървъра

# Автоматичен метод — препоръчително
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip

# Ръчен метод — ако ssh-copy-id не е наличен
cat ~/.ssh/id_ed25519.pub | ssh user@server-ip \
  "mkdir -p ~/.ssh && chmod 700 ~/.ssh && \
   cat >> ~/.ssh/authorized_keys && \
   chmod 600 ~/.ssh/authorized_keys"

Права на файловете — критично важно

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

# На сървъра
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

# На клиента
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519        # Частен ключ — само аз
chmod 644 ~/.ssh/id_ed25519.pub    # Публичен ключ — четим

4. Hardening на sshd_config — задължителни настройки {#sshd-config}

Файлът /etc/ssh/sshd_config е централното място за hardening на SSH сървъра. Ето моята базова конфигурация с обяснения за всяка директива:

# /etc/ssh/sshd_config — Hardened конфигурация

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ОСНОВНИ НАСТРОЙКИ
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Слушаме на нестандартен порт — намалява шума от ботове
# ВАЖНО: Не е сигурност сама по себе си! Само намалява логовете.
Port 2222

# Само IPv4 или IPv6 — изберете едно ако не ползвате двете
#AddressFamily inet

# Слушаме само на конкретен интерфейс (ако е приложимо)
#ListenAddress 192.168.1.10

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# АУТЕНТИКАЦИЯ
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Забраняване на root login — ЗАДЪЛЖИТЕЛНО
PermitRootLogin no

# Само публични ключове — пароли са забранени
PasswordAuthentication no
PermitEmptyPasswords no

# Забраняване на keyboard-interactive (използва се за пароли)
# ИЗКЛЮЧЕНИЕ: Ако ползваме MFA, трябва да е "yes"
ChallengeResponseAuthentication no

# Само публично-ключова аутентикация
PubkeyAuthentication yes

# Забраняване на PAM (ако не е нужен за MFA)
# UsePAM no  # Оставяме yes ако ползваме MFA с pam_google_authenticator

# Максимален брой опити за аутентикация
MaxAuthTries 3

# Максимален брой едновременни неаутентикирани сесии
MaxStartups 10:30:60

# Timeout за логин — ако не се аутентикираш в рамките на 30 сек, сесията се прекратява
LoginGraceTime 30

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ОГРАНИЧАВАНЕ НА ДОСТЪПА
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Позволяваме само конкретни потребители
AllowUsers deploy admin myuser

# ИЛИ позволяваме само конкретни групи
# AllowGroups sshusers admins

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# КРИПТОГРАФСКИ НАСТРОЙКИ
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Само съвременни алгоритми за обмен на ключове
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

# Само силни cipher-и
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr

# Само силни MAC алгоритми
MACs [email protected],[email protected],[email protected]

# Само Ed25519 и ECDSA host ключове
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_ecdsa_key
# Премахваме RSA host ключа от ротацията (незадължително)

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ФУНКЦИОНАЛНОСТ — ИЗКЛЮЧВАМЕ НЕНУЖНОТО
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Забраняване на X11 forwarding ако не е нужно
X11Forwarding no

# Забраняване на TCP forwarding ако не е нужно
# AllowTcpForwarding no  # Внимание: блокира и легитимни tunnel-и

# Забраняване на agent forwarding по подразбиране
AllowAgentForwarding no

# Забраняване на ненужни функции
PrintMotd no
PrintLastLog yes

# Показване на последното неуспешно влизане
PrintLastLog yes

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# KEEPALIVE И TIMEOUT
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Изпращаме keepalive пакети на клиента
ClientAliveInterval 300    # На всеки 5 минути
ClientAliveCountMax 2      # Максимум 2 опита — след 10 мин без отговор, прекъсваме

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ЛОГВАНЕ
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# Подробно логване на аутентикацията
LogLevel VERBOSE
SyslogFacility AUTH

Проверка и рестарт

# Проверяваме конфигурацията за грешки ПРЕДИ рестарт
sudo sshd -t

# Ако няма грешки, рестартираме
sudo systemctl reload sshd

# ВАЖНО: Преди да затворя текущата сесия, отварям НОВА в друг терминал
# и проверявам дали мога да се логна с новите настройки!

5. Multi-Factor Authentication (MFA) върху SSH {#mfa}

SSH ключовете са много добри, но MFA добавя още един слой: дори при компрометиран частен ключ, атакуващият се нуждае и от втория фактор.

Инсталация на Google Authenticator PAM модул

# Debian/Ubuntu
sudo apt install libpam-google-authenticator

# RHEL/CentOS/Rocky Linux
sudo dnf install google-authenticator

Конфигурация за всеки потребител

# Изпълнявам като потребителя, за когото настройвам MFA
google-authenticator

# Отговарям:
# Do you want authentication tokens to be time-based? → Y
# Update your .google_authenticator file? → Y
# Disallow multiple uses of the same token? → Y
# Increase the original generation time limit? → N
# Enable rate limiting? → Y

Сканирам QR кода с Google Authenticator, Authy или друго TOTP приложение и съхранявам emergency scratch codes на сигурно място.

Конфигурация на PAM

# /etc/pam.d/sshd
# Добавям В НАЧАЛОТО на файла:
auth required pam_google_authenticator.so nullok
# nullok = позволява вход без MFA за потребители, които не са го настроили
# Премахни nullok когато всички потребители имат MFA

Конфигурация на sshd_config за MFA

# /etc/ssh/sshd_config

# Задължително за MFA
UsePAM yes
ChallengeResponseAuthentication yes

# Изискваме ДВАТА фактора: ключ + TOTP код
AuthenticationMethods publickey,keyboard-interactive

# ИЛИ позволяваме само ключ ИЛИ ключ+MFA (по-малко сигурно)
# AuthenticationMethods publickey keyboard-interactive

Резултат от MFA конфигурацията

$ ssh myuser@server
Authenticated with partial success.
Verification code: [въвеждам 6-цифрен TOTP код]

Welcome to Ubuntu 22.04.3 LTS
Last login: Mon Feb 17 14:22:01 2025 from 192.168.1.100

6. Управление на достъпа: принципът на минималните привилегии {#access-control}

Не всеки трябва да може всичко

Организирам SSH достъпа на принципа "least privilege" — всеки потребител получава само толкова достъп, колкото му е нужен.

# В sshd_config — Match блокове за различни потребители

# Deployment потребител — само конкретни команди
Match User deploy
    ForceCommand /opt/scripts/deploy.sh
    PermitTTY no
    AllowTcpForwarding no
    X11Forwarding no

# Мониторинг потребител — само четене, без port forwarding
Match User monitoring
    ForceCommand /usr/bin/monitoring-agent
    PermitTTY no
    AllowTcpForwarding no

# Администратори — пълен достъп, но само от вътрешната мрежа
Match User admin Address 10.0.0.0/8
    PermitRootLogin no
    AllowTcpForwarding yes

Ограничаване на authorized_keys

В ~/.ssh/authorized_keys мога да добавя ограничения директно към конкретен ключ:

# Ключът може да се използва само за конкретна команда
command="/usr/bin/backup-script",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... backup-key

# Ключът е валиден само от конкретни IP адреси
from="192.168.1.0/24,10.0.0.5",no-pty ssh-ed25519 AAAA... office-laptop

# Ключът изтича след определена дата (OpenSSH 8.2+)
# Използвам SSH certificate authorities за expiry — вижте секция 9

Забрана на root login — задължително

Директният root login е антипатерн. Вместо това:

# Добавям потребителя в sudo групата
sudo usermod -aG sudo myuser

# Или за специфични команди — /etc/sudoers.d/myuser
myuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

7. Port forwarding, jump hosts и ProxyJump {#port-forwarding}

SSH като тунел

Едно от най-мощните (и опасни, ако е неконтролирано) features на SSH е port forwarding:

# Local port forwarding — достъп до remote MySQL от localhost
ssh -L 3306:localhost:3306 user@production-server
# Сега mysql -h 127.0.0.1 се свързва към production MySQL

# Remote port forwarding — излагане на local сервиз към remote
ssh -R 8080:localhost:3000 user@public-server
# public-server:8080 сочи към моето localhost:3000

# Dynamic SOCKS proxy — цял браузър/приложение през SSH тунел
ssh -D 1080 user@server

Jump Hosts / Bastion Servers

При архитектура с bastion host, всички SSH连接 минават през него:

Internet → Bastion Host → Internal Servers
# ~/.ssh/config — конфигурация за jump host
Host bastion
    HostName bastion.example.com
    User admin
    IdentityFile ~/.ssh/id_ed25519
    Port 2222

Host internal-server
    HostName 10.0.1.50
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion
    # Или алтернативно: ProxyCommand ssh -W %h:%p bastion

# Сега просто:
ssh internal-server
# Автоматично минава през bastion

Многостъпков ProxyJump

# Верига от jump hosts
ssh -J bastion1,bastion2 final-server

# В ~/.ssh/config
Host deep-internal
    HostName 10.10.10.5
    ProxyJump bastion1,bastion2

8. Мониторинг, логове и алертинг {#monitoring}

Какво да следя

Всяка SSH сесия трябва да бъде логвана. Следя за:

  • Неуспешни опити за аутентикация (brute force)
  • Успешни логини от нови IP адреси
  • Логини в необичайни часове
  • Множество паралелни сесии

Четене на auth логовете

# Последните неуспешни опити
sudo grep "Failed password" /var/log/auth.log | tail -20

# Успешни логини
sudo grep "Accepted publickey" /var/log/auth.log

# Всички SSH events
sudo journalctl -u ssh --since "1 hour ago"

# Статистика по IP адрес (кой атакува най-много?)
sudo grep "Failed" /var/log/auth.log | \
  awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

Fail2ban — автоматичен отговор на brute force

# Инсталация
sudo apt install fail2ban

# /etc/fail2ban/jail.local

[sshd]

enabled = true port = 2222 # Моят нестандартен порт filter = sshd logpath = /var/log/auth.log maxretry = 3 # 3 грешни опита bantime = 3600 # Бан за 1 час findtime = 600 # В рамките на 10 минути ignoreip = 127.0.0.1/8 192.168.1.0/24 # Whitelist за вътрешна мрежа

# Рестартиране и проверка
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

# Ръчно деблокиране на IP
sudo fail2ban-client set sshd unbanip 1.2.3.4

Автоматичен алертинг при нов логин

Добавям в /etc/ssh/sshrc (изпълнява се при всяко успешно логване):

#!/bin/bash
# /etc/ssh/sshrc — изпълнява се при успешен SSH логин

IP=$(echo $SSH_CONNECTION | awk '{print $1}')
USER=$(whoami)
HOSTNAME=$(hostname)
DATE=$(date '+%Y-%m-%d %H:%M:%S')

# Изпращаме имейл/webhook notification
curl -s -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
  -H 'Content-type: application/json' \
  --data "{\"text\":\"🔐 SSH Login: ${USER}@${HOSTNAME} from ${IP} at ${DATE}\"}"

9. Автоматизация и управление на ключове в мащаб {#automation}

SSH Certificate Authorities (CA)

При 10+ сървъра, управлението на authorized_keys файлове е кошмар. Решението е SSH CA:

# 1. Генериране на CA ключ (пазя го ИЗКЛЮЧИТЕЛНО сигурно!)
ssh-keygen -t ed25519 -f ~/.ssh/ssh_ca -C "My SSH Certificate Authority"

# 2. Подписване на потребителски ключ
ssh-keygen -s ~/.ssh/ssh_ca \
  -I "myuser@company" \              # Идентификатор
  -n "myuser,deploy" \               # Валидни потребителски имена
  -V "+30d" \                        # Валиден 30 дни
  ~/.ssh/id_ed25519.pub

# Създава се ~/.ssh/id_ed25519-cert.pub

# 3. На ВСЕКИ сървър добавяме само:
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ssh_ca.pub

# Вече НЕ е нужно да копирам ключове на всеки сървър!
# CA подпис = достъп до всички сървъри, конфигурирани с тоя CA

Предимства: Централизиран контрол, автоматичен expiry, лесна ревокация.

Ansible за управление на ключове

# tasks/ssh_keys.yml
- name: Add SSH public keys to authorized_keys
  ansible.posix.authorized_key:
    user: "{{ item.user }}"
    key: "{{ item.key }}"
    state: present
    exclusive: false
  loop: "{{ ssh_authorized_keys }}"

- name: Harden sshd_config
  ansible.builtin.template:
    src: sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: '0600'
    validate: /usr/sbin/sshd -t -f %s
  notify: reload sshd

HashiCorp Vault за динамични SSH ключове

За максимална сигурност, използвам Vault's SSH Secrets Engine:

# Vault генерира OTP (One-Time Password) за конкретна сесия
vault ssh -mode=otp -role=my-role user@server
# Паролата е валидна САМО за тази конкретна сесия

10. Бърза чеклиста за hardening {#checklist}

Използвам тази чеклиста при всеки нов сървър:

✅ Аутентикация

  • [ ] SSH ключове с Ed25519 или ECDSA-521 алгоритъм
  • [ ] Passphrase на всички частни ключове
  • [ ] PasswordAuthentication no в sshd_config
  • [ ] PermitRootLogin no в sshd_config
  • [ ] MFA с TOTP (Google Authenticator / Authy)
  • [ ] MaxAuthTries 3 настроено

✅ Криптография

  • [ ] Само Ed25519/ECDSA host ключове
  • [ ] Ограничени KexAlgorithms до curve25519 и DH group 16+
  • [ ] Само AES-GCM и ChaCha20-Poly1305 cipher-и
  • [ ] Само ETM MAC алгоритми

✅ Мрежа и достъп

  • [ ] Нестандартен порт (намалява шума)
  • [ ] AllowUsers или AllowGroups ограничение
  • [ ] Fail2ban инсталиран и конфигуриран
  • [ ] Firewall правила ограничаващи SSH до конкретни IP-та (ако е приложимо)

✅ Мониторинг

  • [ ] LogLevel VERBOSE в sshd_config
  • [ ] Centralized log aggregation (ELK, Grafana Loki, или друго)
  • [ ] Алертинг при успешни логини
  • [ ] Редовен преглед на auth.log

✅ Управление

  • [ ] Политика за ротация на SSH ключове (на 1-2 години)
  • [ ] Процедура за ревокация при компрометиран ключ
  • [ ] SSH CA за среди с много сървъри
  • [ ] Документирани всички публични ключове и техните собственици

Заключение

SSH сигурността не е еднократно действие — тя е процес. Заплахите еволюират, алгоритмите остаряват, конфигурациите трябва да се преглеждат периодично.

Моето правило е просто: всеки SSH достъп трябва да изисква нещо, което притежавам (ключ) и нещо, което знам (passphrase + TOTP). Паролите като единствен фактор са история.

Инструментите са безплатни, конфигурацията отнема час, а защитата е реална. Няма оправдание да не го направим.


Статията е базирана на OpenSSH документация, CIS Benchmarks за Linux и NIST SP 800-53 препоръки за access control. Всички команди са тествани на OpenSSH 8.9+ и Ubuntu 22.04 / Debian 12.

open source spirit
🛠️
$

Намерихте материала за полезен?

Съдържанието на itpraktika.com е безплатно и ще остане такова.
Ако статията ти е помогнала — можеш да подкрепиш сайта с малка доброволна сума. Всяко дарение помага за поддръжката и развитието на портала.

PayPal Revolut

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *


Колко е 5 + 8 ? (въведете числото)