~/yazilar/ssh-sikilastirma
SSH'ı biraz daha güvenli hale getirmek
içindekiler
Dışarıdan erişilebilen bir sunucunun SSH loglarına bir gün sonra baktığında tablo hep aynı oluyor: tanımadığın adreslerden root, admin, oracle, test gibi kullanıcı adlarıyla sırayla deneme yapılıyor.
sudo journalctl -u ssh --since "24 hours ago" | grep -E "Invalid user|Failed password" | tail -n 4... sshd[2211]: Invalid user admin from 203.0.113.45 port 51122
... sshd[2211]: Failed password for invalid user admin from 203.0.113.45 port 51122 ssh2
... sshd[2240]: Failed password for root from 198.51.100.7 port 40312 ssh2
... sshd[2263]: Invalid user oracle from 203.0.113.45 port 52990Bunlar hedefli saldırı değil, internetin arka plan gürültüsü. Parolan güçlüyse büyük ihtimalle bir şey olmaz. Ben "büyük ihtimalle" ile yetinmek istemiyorum, parola ile girişi tamamen kapatınca bu denemelerin hiçbirinin şansı kalmıyor. Sadece kampüs ağından erişilen sunucular için de durum çok farklı değil. Ağın içinde ele geçirilmiş tek bir makine aynı taramayı içeriden yapabilir.
Sıra şöyle: önce anahtarla girişi kurup çalıştığını görüyorum, sonra parola ve root girişini kapatıyorum, en son fail2ban ve UFW geliyor. Sıra önemli. Tersini yaparsan kapıyı içeriden kilitleyip anahtarı dışarıda bırakırsın.
Kendini kilitlememek için tek kural
Bu yazıdaki her değişiklikten sonra açık olan SSH oturumunu kapatmıyorum. Yeni bir terminal açıp ikinci bir oturumla bağlanmayı deniyorum. İkinci oturum açılıyorsa devam ediyorum, açılmıyorsa birinci oturumdan düzeltiyorum. sshd yeniden başlasa da kurulmuş bağlantılar kopmuyor, yani o ilk oturum senin can simidin.
Bunun yanında bir yedek yolun olsun. Sanal makinedeysen hipervizör konsoluna, fiziksel sunucudaysan iDRAC, iLO gibi uzaktan yönetim kartına girebildiğini başlamadan önce kontrol et. Gece yarısı veri merkezine gitmek kimsenin istediği şey değil.
Anahtar çifti ve sunucuya kopyalamak
Anahtarı kendi bilgisayarında üretiyorsun, sunucuda değil:
ssh-keygen -t ed25519 -C "ad.soyad@dizustu"Ed25519 anahtarları kısa ve hızlı, güncel her OpenSSH sürümü destekliyor. Çok eski bir cihaza da bağlanman gerekiyorsa ikinci seçenek -t rsa -b 4096. Passphrase sorusunu boş geçmiyorum. Dizüstü çalınırsa ya da ev dizini bir yedekte açıkta kalırsa anahtar tek başına işe yaramasın. Her bağlantıda passphrase yazmak can sıkıyorsa ssh-agent bunu oturum başına bir kereye indiriyor.
Açık anahtarı sunucuya gönder:
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@192.0.2.10ssh-copy-id anahtarı sunucudaki ~/.ssh/authorized_keys dosyasına ekliyor ve izinleri ayarlıyor. Windows'ta bu komut yok, açık anahtarın içeriğini sunucuda o dosyaya elle ekleyebilirsin. Elle eklediysen izinlere bak:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keysBulut image'larında parolayla SSH girişi çoğu zaman baştan kapalı geliyor ve ssh-copy-id bağlanamıyor. O durumda sağlayıcının kurulumda root'a (ya da ubuntu gibi varsayılan kullanıcıya) koyduğu anahtarla bağlanıp dosyayı yeni kullanıcıya kopyalamak en kısa yol:
sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
sudo install -m 600 -o deploy -g deploy /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keyssshd varsayılan olarak StrictModes açık çalışıyor ve başkalarının yazabildiği bir .ssh dizinini ya da authorized_keys dosyasını sessizce yok sayıyor. İstemci tarafında hiçbir hata görmüyorsun, sadece anahtar yerine parola soruluyor. Sunucu logunda ise şuna benzer bir satır çıkıyor:
Authentication refused: bad ownership or modes for directory /home/deploy/.sshŞimdi anahtarın gerçekten çalıştığını, istemciye sadece anahtar kullanmasını söyleyerek test et:
ssh -o PreferredAuthentications=publickey deploy@192.0.2.10İçeri girdiysen tamam. Permission denied (publickey) alıyorsan bir sonraki bölüme geçme, önce bunu çöz. ssh -v çıktısında hangi anahtarın denendiğini görebilirsin.
Ayarları sshd_config.d altında tut
Ubuntu'daki /etc/ssh/sshd_config dosyasının en başında şu satır var:
Include /etc/ssh/sshd_config.d/*.confAna dosyaya dokunmak yerine kendi ayarlarımı bu dizine ayrı bir dosya olarak koyuyorum. Paket güncellemesi sshd_config dosyasının yeni bir sürümünü getirdiğinde "bu dosyayı değiştirmişsin, ne yapayım?" sorusuyla uğraşmıyorum. Bütün sunuculara aynı dosyayı Ansible gibi bir araçla dağıtmak da kolaylaşıyor.
Burada bilmen gereken bir tuhaflık var: sshd çoğu ayar için ilk okuduğu değeri kullanıyor, sonra gelenleri yok sayıyor. Include satırı dosyanın en başında olduğu için sshd_config.d altındaki dosyalar ana dosyayı eziyor, kendi aralarında da alfabetik sırada önce gelen kazanıyor. Bulut image'larında bu dizinde PasswordAuthentication yes içeren hazır bir dosya gelebiliyor. Kendi dosyamın adını 10- ile başlatmamın sebebi bu, ondan önce okunsun.
SSH'a kimlerin girebileceğini bir grupla sınırlamak istiyorum. Grubu ayarlardan önce oluşturup kendimi içine alıyorum:
sudo groupadd ssh-erisim
sudo usermod -aG ssh-erisim deploy
id deployAyar dosyası:
# /etc/ssh/sshd_config.d/10-sertlestirme.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowGroups ssh-erisimKbdInteractiveAuthentication satırını atlama. Parolayı kapatıp bunu açık bırakırsan PAM üzerinden klavye etkileşimli giriş hâlâ parola sorabiliyor. Eski yazılarda bu ayarın adı ChallengeResponseAuthentication olarak geçiyor, yeni sürümler eski adı hâlâ takma ad olarak kabul ediyor.
AllowGroups bu dosyadaki en etkili satır ama en tehlikelisi de o. Kendi kullanıcın grupta değilse dışarıda kalırsın. id deploy çıktısında ssh-erisim görmeden devam etme.
MaxAuthTries 3 ise bir yan etki yapabiliyor. ssh-agent içinde çok sayıda anahtar varsa istemci hepsini sırayla deniyor ve doğru anahtara gelmeden hakkı bitiyor:
Received disconnect from 192.0.2.10 port 22:2: Too many authentication failuresÇözümü istemci tarafında. ~/.ssh/config içinde o sunucu için hangi anahtarın kullanılacağını söylüyorsun:
Host web01
HostName 192.0.2.10
User deploy
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yesUygulamadan önce sözdizimini ve birleşmiş son hali kontrol et:
sudo sshd -t
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|allowgroups)'permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
allowgroups ssh-erisimsshd -t hiçbir şey yazmıyorsa sözdizimi doğru. sshd -T ise bütün dosyalar birleştikten sonra geçerli olan değerleri gösteriyor. Hangi dosyanın kazandığından emin değilsen bakacağın yer burası. Burada passwordauthentication yes görüyorsan dizinde senin dosyandan önce okunan başka bir dosya var.
Her şey yerindeyse servisi yeniden başlat:
sudo systemctl restart sshVe ilk oturumu kapatmadan ikinci terminalden üç test:
ssh deploy@192.0.2.10 # açılmalı
ssh -o PubkeyAuthentication=no deploy@192.0.2.10 # reddedilmeli
ssh root@192.0.2.10 # reddedilmeliSon ikisinde beklediğim çıktı bu:
deploy@192.0.2.10: Permission denied (publickey).Parola sorulmuyor bile. Sunucu sadece anahtar kabul ettiğini en başta söylüyor.
Socket activation ile gelen sürümler
Ubuntu'nun yeni sürümlerinde SSH socket activation ile geliyor. 22. portu sshd değil systemd dinliyor, bağlantı gelince sshd başlatılıyor. ss -tlpn çıktısında port 22'nin karşısında sshd yerine systemd görmenin sebebi bu. Hangi modda olduğunu şöyle görüyorsun:
systemctl is-enabled ssh.socket ssh.service
systemctl status ssh.socket --no-pagerGünlük kullanımda fark etmiyor ama iki yerde karşına çıkıyor. Birincisi port ya da dinlenen adres. Bu modda sshd_config içindeki Port ve ListenAddress satırlarını bir generator okuyup ssh.socket ayarına çeviriyor. Sadece restart ssh yaparsan sshd yeni portu bilir ama systemd eski portu dinlemeye devam eder. Port değiştirdiysen şu ikisi gerekiyor:
sudo systemctl daemon-reload
sudo systemctl restart ssh.socketİkincisi, bazı yönetim araçları ve eski dokümanlar sshd'nin hep çalıştığını varsayıyor. systemctl status ssh çıktısında bağlantı yokken inactive görmek bu modda normal.
Port değiştirmeyi ben güvenlik önlemi saymıyorum. Log gürültüsünü azaltıyor, o kadar. Parola kapalıyken o gürültünün zararı da yok. Yine de değiştireceksen aşağıdaki UFW kuralını yeni porta göre yaz, yoksa güvenlik duvarını açtığın anda kendini dışarıda bırakırsın.
fail2ban
Parola kapalıyken kaba kuvvet denemelerinin başarı şansı yok. Bu yüzden fail2ban'i burada ek bir güvenlik katmanından çok temizlik aracı olarak görüyorum: aynı adresten dakikada onlarca bağlantı açan bir şeyi bir süreliğine susturuyor, loglar okunur kalıyor.
sudo apt install fail2banUbuntu paketi /etc/fail2ban/jail.d/defaults-debian.conf ile sshd jail'ini açık getiriyor. Ben yine de değerleri kendim belirlemek için .local uzantılı bir dosya ekliyorum. .conf dosyaları paketle birlikte değişebilir, .local dosyalarına paket dokunmuyor ve aynı ayarlar için onlar kazanıyor.
# /etc/fail2ban/jail.d/sshd.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
# Yönetim ağını hiçbir zaman yasaklama
ignoreip = 127.0.0.1/8 ::1 10.10.0.0/16
[sshd]
enabled = true
backend = systemdignoreip satırına kendi yönetim ağını ya da VPN adres havuzunu yazmak bence şart. Örnekteki 10.10.0.0/16 sadece bir örnek. Hangi adresten bağlandığını echo $SSH_CLIENT gösteriyor, ilk alan istemcinin adresi. NAT arkasındaki bir ofisten yanlış anahtarla birkaç kez denendiğinde o çıkış adresinin arkasındaki herkes bir saatliğine dışarıda kalır.
backend = systemd logları dosyadan değil journal'dan okutuyor. /var/log/auth.log dosyasının olmadığı minimal kurulumlarda varsayılan backend ile fail2ban hiç açılmayabiliyor, journal ise her zaman orada.
sudo systemctl restart fail2ban
sudo fail2ban-client status sshdStatus for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 37
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 203.0.113.45Jail'in açık görünmesi, filtrenin logları gerçekten yakaladığını göstermiyor. Bunu ayrıca deniyorum:
sudo fail2ban-regex systemd-journal sshdÇıktının sonundaki Lines: satırında matched sayısı sıfırsa filtre hiçbir satırı tanımıyor demek, jail boşuna çalışıyor.
Yanlışlıkla yasaklanan bir adresi elle açmak için:
sudo fail2ban-client set sshd unbanip 198.51.100.7UFW
Kendini kilitleme ihtimali en yüksek adım bu. ufw enable dediğin anda varsayılan politika gelen her şeyi reddetmeye başlıyor. SSH'a izin veren kural o anda yoksa açık oturum bağlantı takibi sayesinde çoğu zaman ayakta kalıyor ama yeni bağlantı açamıyorsun, o oturum bir kez koptuğunda da geri dönemiyorsun.
O yüzden önce kuralları ekliyorum, sonra açıyorum. ufw kapalıyken eklenen kurallar kaydediliyor ama uygulanmıyor:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 10.10.0.0/16 to any port 22 proto tcp comment 'SSH yonetim agi'
sudo ufw show addedAdded user rules (see 'ufw status' for running firewall):
ufw allow from 10.10.0.0/16 to any port 22 proto tcp comment 'SSH yonetim agi'ufw enable demeden önce echo $SSH_CLIENT ile bağlandığın adresin bu aralıkta olduğunu kontrol et ve aralığı kendi ağına göre değiştir. Adresin aralığın dışındaysa yeni oturum açamazsın. Aşağıdaki timer seni kurtarır ama beş dakikan gider.
SSH'ı sadece yönetim ağına açmak en temizi. Sunucuya dışarıdan erişilmesi gerekiyorsa ve araya VPN koyamıyorsan allow yerine limit kullanıyorum. sudo ufw limit 22/tcp aynı adresten son 30 saniyede altı ya da daha fazla bağlantı denemesi geldiğinde yenilerini reddediyor.
Açmadan önce bir emniyet kemeri takıyorum: beş dakika sonra güvenlik duvarını kendiliğinden kapatacak geçici bir timer. Her şey yolunda giderse iptal ediyorum, gitmezse beş dakika beklemek yetiyor.
sudo systemd-run --unit=ufw-geri-al --on-active=5m /usr/sbin/ufw disable
sudo ufw enableCommand may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startupİkinci terminalden yeni bir SSH oturumu aç. Açılıyorsa timer'ı iptal et ve son duruma bak:
sudo systemctl stop ufw-geri-al.timer
sudo ufw status verboseStatus: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN 10.10.0.0/16 # SSH yonetim agiBu sunucuya ileride bir web servisi koyduğunda 80 ve 443 için ayrı kural ekleyeceksin. Docker kuracaksan şunu şimdiden bil: Docker'ın yayınladığı portlar UFW kurallarının etrafından dolaşıyor. Nedenini Docker'a giriş yazısında ayrıntısıyla anlatıyorum.
Artık sunucuya erişim yönetimi iki dosyaya iniyor. Yeni biri gelince açık anahtarını authorized_keys dosyasına ekleyip kullanıcıyı ssh-erisim grubuna alıyorsun, ayrılan biri için tersini yapıyorsun.
Sıradaki yazı: Ubuntu'da güvenlik güncellemelerini otomatiğe bağlamak