Ръководство за сигурен 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.
Намерихте материала за полезен?
Съдържанието на itpraktika.com е безплатно и ще остане такова.
Ако статията ти е помогнала — можеш да подкрепиш сайта с малка доброволна сума.
Всяко дарение помага за поддръжката и развитието на портала.
