Tek sunuculu küçük ekip için Linux sertleştirme: SSH, güvenlik duvarı, otomatik güncelleme
Küçük ekip için Linux sunucu sertleştirme: SSH'ı anahtara indirme, ufw/firewalld ve fail2ban, otomatik güvenlik güncellemeleri ve günlük Python denetim betiği.
Linux sunucu sertleştirme, tek bir sanal sunucu üzerinde iş yapan küçük ekibin o sunucuyu internetin sürekli deneme yanılma trafiğine karşı birkaç saatlik çalışmayla savunulabilir hale getirmesidir. Küçük ve orta ölçekli işletmelerde otomasyonlar, raporlama işleri ve iç araçlar çoğu zaman bir ya da iki sanal sunucuda yaşar; bu makineler kurulduğu gün parola ile SSH girişine açık, tüm portları dinler durumda ve güvenlik yamalarını elle bekler halde bırakılır.
Herhangi bir sunucuyu internete bağladıktan dakikalar sonra günlüklerde SSH parola denemeleri görünür; bu trafik size özel değildir, adres aralıklarını tarayan botların olağan davranışıdır. Sorun, savunmanın karmaşık olması değil, hiç yapılmamasıdır. Tam kapsamlı bir güvenlik programı başka bir yazının konusu; burada tek sunuculu ekibin bir öğleden sonrada bitirebileceği, geri alması kolay ve ölçülebilir dört adım var.
Yazıdaki komutlar AlmaLinux 9 ve Ubuntu 22.04 ile 24.04 için verilmiştir; iki dağıtım arasındaki farklar ilgili yerde belirtilir. Sıra önemlidir: önce SSH erişimi daraltılır, sonra güvenlik duvarı ve brute force freni kurulur, ardından güvenlik güncellemeleri otomatiğe bağlanır ve son olarak bu durumun bozulmadığını her gün doğrulayan küçük bir denetim betiği eklenir.
SSH erişimini anahtara ve belirli kullanıcılara indirmek
SSH sertleştirme, parola ile girişi kapatıp yalnız açık anahtarla kimlik doğrulamaya izin vermek, root kullanıcısının doğrudan bağlanmasını engellemek ve bağlanabilecek kullanıcı listesini açıkça yazmaktır. Parola denemeleri kapalı olduğunda botların sözlük saldırısı anlamını yitirir; root kapalı olduğunda saldırganın hem kullanıcı adını hem anahtarı bilmesi gerekir.
Ayarlar ana yapılandırma dosyasına değil, sshd_config.d altındaki bir ek dosyaya yazılır. Paket güncellemeleri ana dosyayı değiştirmek isteyebilir; ek dosya bu çakışmadan etkilenmez ve ne değiştirdiğinizi tek bakışta gösterir. OpenSSH belgelerine göre bir anahtar için ilk okunan değer geçerlidir ve ek dosyalar ana dosyanın başındaki Include satırıyla önce okunur, bu yüzden ek dosyadaki satır kazanır.
# Önce yerel makineden anahtarı kopyala ve anahtarla girebildiğini doğrula:
# ssh-copy-id kullanici@sunucu && ssh kullanici@sunucu
# Sunucuda, mevcut oturumu KAPATMADAN:
sudo install -d -m 755 /etc/ssh/sshd_config.d
sudo tee /etc/ssh/sshd_config.d/10-sertlestirme.conf > /dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
AllowUsers kullanici otomasyon
EOF
sudo sshd -t && sudo systemctl reload sshd # Ubuntu'da birim adı: ssh
# İkinci bir terminalden yeni bağlantı aç; girebiliyorsan ilk oturumu kapat.
Ek dosyadaki satırların her birinin ayrı bir gerekçesi vardır:
PasswordAuthenticationveKbdInteractiveAuthenticationbirlikte kapatılır; yalnız ilkini kapatmak, bazı yapılandırmalarda parola istemini klavye etkileşimli yoldan açık bırakır.MaxAuthTries 3veLoginGraceTime 30, bir bağlantının deneme sayısını ve kimlik doğrulamasız açık kalma süresini kısar.AllowUserssatırına yalnız gerçekten bağlanacak hesaplar yazılır; otomasyonun kendi kullanıcısı ayrı tutulur ki anahtarı sızdığında yalnız o hesap iptal edilsin.- Root yerine
sudoyetkili bir kullanıcı kullanılır; kimin ne çalıştırdığı günlükte kullanıcı adıyla görünür.
Kendinizi dışarıda bırakmamak için iki kural vardır: sshd -t sözdizimi hatasını yeniden yüklemeden önce yakalar ve mevcut oturum, yeni bir terminalden girişi doğrulayana kadar açık tutulur. Yeniden yükleme açık oturumları düşürmez. Portu değiştirmek gerçek bir güvenlik önlemi değildir, yalnız günlük gürültüsünü azaltır; yine de değiştirecekseniz AlmaLinux'ta SELinux için semanage port ile yeni portu tanıtmayı unutmayın.
Güvenlik duvarı ve brute force freni
Güvenlik duvarı, sunucuya dışarıdan yalnız bilinçli olarak açtığınız portlardan ulaşılmasını sağlayan ilk katmandır; fail2ban ise günlükleri okuyup aynı adresten gelen ardışık başarısız girişleri geçici olarak engelleyen ikinci katmandır. Birincisi hangi kapıların var olduğunu belirler, ikincisi var olan kapıya yüklenenleri yavaşlatır. İkisi birden kurulduğunda botların maliyeti, ilgisini kaybedeceği düzeye çıkar.
Varsayılan kural gelen her şeyi reddetmek, ardından yalnız gereken servisleri açmaktır. Veri tabanı, önbellek ya da iç yönetim paneli gibi servisler internete açılmaz; onlara yalnız 127.0.0.1 üzerinden ya da SSH tüneliyle erişilir.
# Ubuntu: ufw + fail2ban
sudo apt install -y ufw fail2ban
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
# AlmaLinux: firewalld + fail2ban (EPEL deposundan)
sudo dnf install -y epel-release
sudo dnf install -y firewalld fail2ban fail2ban-firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service={ssh,http,https}
sudo firewall-cmd --reload
# fail2ban SSH hapishanesi; iki dağıtımda da aynı dosya
sudo tee /etc/fail2ban/jail.d/sshd.local > /dev/null <<'EOF'
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Kuralın gerçekten işlediğini içeriden değil dışarıdan doğrulamak gerekir. Sunucuda ss -tlnp hangi servisin hangi adresi dinlediğini gösterir; yerel makineden çalıştırılan nmap -Pn sunucu-adresi ise internetin gördüğü açık portları listeler. İki liste karşılaştırıldığında yalnız SSH, HTTP ve HTTPS kalmalıdır; başka bir port görünüyorsa ya servis yanlış adrese bağlanmıştır ya da güvenlik duvarı kuralı eksiktir.
fail2ban'de backend = systemd satırı, günlükleri doğrudan journald'den okutur; AlmaLinux 9 kurulumlarında klasik günlük dosyası bulunmayabildiği için bu satır olmadan hapishane sessizce boş kalır. Ubuntu'da da çalışır. bantime değerini günlerce uzatmak cazip görünür, ancak paylaşımlı NAT arkasındaki meşru kullanıcıları da kilitler; bir saat, botları caydırmaya yeter.
Güvenlik güncellemelerini otomatiğe bağlamak
Otomatik güvenlik güncellemesi, dağıtımın yalnız güvenlik deposundan gelen yamaları her gün insan müdahalesi olmadan kuran, yeni sürüm ve özellik güncellemelerine ise dokunmayan bir zamanlayıcıdır. Küçük ekipte güncellemeyi elle takip eden kişi izne çıktığında ya da işi yoğunlaştığında yamalar haftalarca bekler; zamanlayıcı bu boşluğu kapatır.
# Ubuntu: yalnız güvenlik deposundan otomatik kurulum
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
grep -r Unattended-Upgrade /etc/apt/apt.conf.d/20auto-upgrades
# AlmaLinux: dnf-automatic, yalnız güvenlik güncellemeleri
sudo dnf install -y dnf-automatic
sudo sed -i 's/^upgrade_type.*/upgrade_type = security/; s/^apply_updates.*/apply_updates = yes/' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic.timer
systemctl list-timers dnf-automatic.timer
Zamanlayıcının çalıştığını iki yerden izlersiniz: Ubuntu'da /var/log/unattended-upgrades/ altındaki günlükler ve /var/run/reboot-required dosyasının varlığı, AlmaLinux'ta dnf-automatic için journald kayıtları ve dnf needs-restarting -r komutunun çıkış kodu. Yeniden başlatma gereken bir çekirdek yaması kurulduğunda bu işaretler belirir; denetim betiğine bir satır ekleyerek bunu bildirime bağlamak kolaydır.
Üretim servisinin bir güncellemeyle bozulması endişesi yerindedir, ancak güvenlik yamaları dağıtım tarafından davranış değiştirmeyecek biçimde geri taşınır ve risk, yamasız kalmanın riskinden küçüktür. Yine de iki önlem alınır: çekirdek güncellemesi sonrası yeniden başlatma elle yapılır ve servisin ayağa kalktığı bir sağlık kontrolüyle doğrulanır. Sağlık kontrolünü dışarıdan izlemek için heartbeat ve dead man's switch yazısındaki düzen uygundur.
Durumu her gün doğrulayan denetim betiği
Denetim betiği, sertleştirme adımlarının bugün de yerinde olduğunu kontrol eden, bulgu varsa listeleyip sıfırdan farklı çıkış koduyla biten küçük bir Python programıdır. Sertleştirme bir kez yapılan iş değildir: bir paket güncellemesi SSH ayarını sıfırlayabilir, yeni kurulan bir servis beklenmedik bir portu dışa açabilir, fail2ban bir hata sonrası durabilir. Betik bu kaymayı insan fark etmeden önce yakalar.
import json
import shutil
import subprocess
from pathlib import Path
SSH_BEKLENEN = {
"passwordauthentication": "no",
"kbdinteractiveauthentication": "no",
"permitrootlogin": "no",
"pubkeyauthentication": "yes",
}
IZINLI_PORTLAR = {"22", "80", "443"}
def komut(*args):
if shutil.which(args[0]) is None:
return None
sonuc = subprocess.run(args, capture_output=True, text=True)
return sonuc.stdout if sonuc.returncode == 0 else None
def sshd_ayarlari():
cikti = komut("sshd", "-T") # etkin ayarlar; root ister
if cikti is None: # root değilsek dosyaları sshd'nin okuduğu sırayla oku
dosyalar = sorted(Path("/etc/ssh/sshd_config.d").glob("*.conf"))
dosyalar.append(Path("/etc/ssh/sshd_config"))
satirlar = []
for dosya in dosyalar:
if dosya.exists():
satirlar += dosya.read_text(errors="replace").splitlines()
cikti = "\n".join(s for s in satirlar if s.strip() and not s.lstrip().startswith("#"))
ayar = {}
for satir in cikti.splitlines():
parca = satir.split(None, 1)
if len(parca) == 2:
ayar.setdefault(parca[0].lower(), parca[1].strip().lower()) # ilk değer geçerli
return ayar
def disa_acik_portlar():
cikti = komut("ss", "-tlnH")
if cikti is None:
return None
portlar = set()
for satir in cikti.splitlines():
alanlar = satir.split()
if len(alanlar) < 4:
continue
adres, port = alanlar[3].rsplit(":", 1)
if adres.startswith(("127.", "[::1]")):
continue # yalnız yerel dinleyen servisler sorun değil
portlar.add(port)
return sorted(portlar, key=lambda p: int(p) if p.isdigit() else 0)
def birim_durumu(birim):
cikti = komut("systemctl", "is-active", birim)
return (cikti or "yok").strip()
def guncelleme_etkin():
apt_ayar = Path("/etc/apt/apt.conf.d/20auto-upgrades")
if apt_ayar.exists() and 'Unattended-Upgrade "1"' in apt_ayar.read_text(errors="replace"):
return birim_durumu("apt-daily-upgrade.timer") == "active"
return birim_durumu("dnf-automatic.timer") == "active"
def denetle():
bulgular = []
ayar = sshd_ayarlari()
for anahtar, beklenen in SSH_BEKLENEN.items():
mevcut = ayar.get(anahtar, "(varsayılan)")
if mevcut != beklenen:
bulgular.append("sshd %s=%s, beklenen %s" % (anahtar, mevcut, beklenen))
portlar = disa_acik_portlar()
if portlar is not None:
fazla = [p for p in portlar if p not in IZINLI_PORTLAR]
if fazla:
bulgular.append("dışa açık beklenmeyen port: %s" % ", ".join(fazla))
if not guncelleme_etkin():
bulgular.append("otomatik güvenlik güncellemesi etkin değil")
if birim_durumu("fail2ban.service") != "active":
bulgular.append("fail2ban çalışmıyor")
return bulgular
if __name__ == "__main__":
bulgular = denetle()
print(json.dumps({"temiz": not bulgular, "bulgular": bulgular}, ensure_ascii=False, indent=2))
raise SystemExit(1 if bulgular else 0)
Betik root ile çalışırsa sshd -T çıktısını kullanır; bu çıktı sunucunun gerçekten uyguladığı etkin ayarları verir ve kesin sonuçtur. Root olmadan çalışırsa dosyaları sshd'nin okuduğu sırayla okur ve ilk değeri geçerli sayar, sonuç yaklaşıktır. Günlük cron ile çalıştırılıp çıkış kodu sıfırdan farklı olduğunda e-posta ya da sohbet bildirimi gönderilmesi yeterlidir; temiz günlerde ses çıkarmaması bilinçli bir tercihtir.
Betiği ekibin ihtiyacına göre genişletmek birkaç satırlık iştir; sık eklenen kontroller şunlardır:
- Kabuğu olan kullanıcıların listesi:
/etc/passwdiçinde beklenmeyen bir hesap belirdiğinde uyar. - Yeniden başlatma bekleyen çekirdek güncellemesi: yukarıdaki işaret dosyası ya da komut çıkışı.
- Yetkili anahtar dosyalarının içeriği:
authorized_keysözetini kaydet, değişince bildir. - Disk doluluğu ve sertifika bitiş tarihi: güvenlikle ilgisiz görünür ama kesintilerin en sık nedenidir.
Sertleştirmenin tamamlayıcısı yedektir. Güvenlik duvarı ve fail2ban saldırıyı pahalılaştırır, ancak sunucu yine de bozulabilir; restic ve rclone ile 3-2-1 yedekleme yazısındaki düzen, bu yazıdaki adımlarla aynı öğleden sonraya sığar ve ikisi birlikte küçük ekibin asgari güvenlik tabanını oluşturur.
Sonuç: dört adım, bir öğleden sonra
Linux sunucu sertleştirme, küçük ekip için dört somut adımdan oluşur: SSH'ı anahtara ve belirli kullanıcılara indirmek, güvenlik duvarıyla yalnız gereken portları açıp fail2ban ile denemeleri frenlemek, güvenlik yamalarını otomatiğe bağlamak ve bu durumu her gün doğrulayan bir betik çalıştırmak. Hiçbiri özel araç ya da bütçe istemez; hepsi dağıtımın kendi paketleriyle kurulur.
İlk adım olarak bugün yalnız SSH bölümünü uygulayın: anahtarla girişi doğrulayın, parolayı kapatın, ikinci terminalden girişi sınayın. Yarın güvenlik duvarı ve fail2ban, ertesi gün güncellemeler ve denetim betiği gelir. Her adımın sonunda denetim betiğini çalıştırıp bulgu listesinin kısaldığını görmek, ilerlemeyi ölçülebilir kılar.
Birden fazla sunucunuz varsa ya da üzerinde müşteri verisi işleyen otomasyonlar çalışıyorsa bu taban yetmez; erişim, günlük ve yedek politikasını birlikte kurmak için yazabilirsiniz: iletişim.