Server yang baru menghadap internet tidak menunggu lama untuk dikunjungi: dalam hitungan menit, pemindai otomatis sudah mencoba SSH dictionary attack, mengintip port 3306, dan mengetuk wp-admin di alamat yang bahkan belum dipublikasikan. Hardening produksi adalah disiplin menyongsong kenyataan itu — mengecilkan permukaan serangan sampai yang tersisa hanya pintu yang memang kita buka. Chapter ini melanjutkan fondasi Chapter Security Hardening ke konteks server publik, lalu mempraktikkan bentuk nyatanya: checklist berprioritas, snapshot konfigurasi, dan skrip audit yang mencetak skor plus rekomendasi — semuanya dari fixture yang dibekukan agar hasilnya bisa dibandingkan ulang.

Dari Lab ke Server Publik #

Di laboratorium, kesalahan konfigurasi hanya menghabiskan waktu kita sendiri. Di produksi, asumsinya terbalik: server sedang diserang sekarang, meski belum ada yang berhasil masuk. Perbedaan ini mengubah keputusan-keputusan kecil. Port database 3306 yang mendengarkan di 0.0.0.0 tidak masalah saat database hanya dipakai aplikasi di mesin yang sama lewat 127.0.0.1 — tetapi menjadi pintu terbuka begitu server punya alamat publik. Service cups yang menganggur, avahi-daemon untuk pencarian printer, dan rpcbind warisan NFS semuanya adalah program yang berjalan sebagai root, menunggu permintaan yang tidak akan pernah datang dari pengguna yang sah — tetapi bisa datang dari penyerang.

Prinsip kerjanya tiga kata: default deny, allow by exception. Firewall menolak semua koneksi masuk kecuali port layanan yang memang dijual ke publik; SSH menolak semua metode login kecuali kunci; dan service yang tidak punya alasan hidup dimatikan. Materi firewall dan MAC pernah kita bahas di Chapter Security Hardening dan nftables ; bab ini menyusun semuanya menjadi checklist produksi.

Checklist Hardening Produksi #

Checklist berikut diurutkan menurut prioritas — kerjakan dari atas, jangan melompat ke bawah sebelum atasnya beres:

ItemPrioritasWujud di sistem
SSH kunci saja, tanpa rootkritisPermitRootLogin no, PasswordAuthentication no
Firewall default denykritishanya port layanan (22, 80, 443) yang terbuka
Patch keamanan otomatiskritisunattended-upgrades
Service minimumtinggimatikan cups, avahi-daemon, rpcbind
Aplikasi tanpa roottingginginx sebagai www-data, aplikasi punya user sendiri
Audit trailtinggiauditd merekam syscall sensitif
Backup terverifikasitinggiuji restore berkala, bukan hanya job berjalan
TLS di semua jalurtinggirujukan Chapter TLS dan SSL Certificates
Secrets terkelolatinggirujukan Chapter Secrets dan Credential Management

Tiga item paling atas layak dijelaskan. Pertama, SSH berbasis kunci: begitu PasswordAuthentication dimatikan, dictionary attack kehilangan maknanya — tidak ada kata sandi yang bisa ditebak, dan PermitRootLogin no memaksa penyerang menebak nama user dan kuncinya sekaligus. Kedua, firewall default deny: paket yang tidak menuju port terbuka ditolak diam-diam, dan aturan “buka 22, 80, 443” tiga baris di nftables. Ketiga, unattended-upgrades: patch keamanan Debian dipasang otomatis begitu dirilis, karena jendela antara pengumuman CVE dan eksploitasi massal kini dihitung jam, bukan minggu — kita bahas mekanismenya di Chapter Security Updates .

Snapshot untuk Audit: Tiga Berkas #

Audit membutuhkan data, dan datanya ada di tiga tempat: sshd_config untuk SSH, ss -tln untuk port yang mendengarkan, serta systemctl untuk service aktif. Bundle chapter ini membekukan ketiganya sebagai snapshot bernilai sengaja belum aman — persis kondisi server yang baru dibangun dan belum diaudit. Kita lihat yang pertama:

bash
cat sshd_config.audit

Output yang Dihasilkan:

text
# Snapshot /etc/ssh/sshd_config untuk audit — nilai sengaja belum aman
Port 22
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
MaxAuthTries 6
X11Forwarding no

Enam directive di fixture itu adalah baris pertama yang diperiksa hampir semua audit SSH: PermitRootLogin yes mengizinkan percobaan langsung sebagai root, PasswordAuthentication yes membuka pintu penebakan, dan MaxAuthTries 6 memberi penyerang enam tebakan per koneksi. Kondisi amannya: no, no, dan angka kecil. Dua snapshot lainnya:

bash
cat port-terbuka.txt
echo
cat service-aktif.txt

Output yang Dihasilkan:

text
LISTEN 0.0.0.0:22     sshd
LISTEN 0.0.0.0:80     nginx
LISTEN 0.0.0.0:443    nginx
LISTEN 0.0.0.0:3306   mysqld

ssh.service
nginx.service
mysql.service
cups.service
avahi-daemon.service
rpcbind.service

Port 22, 80, dan 443 memang direncanakan untuk server web ini — SSH, HTTP, dan HTTPS. Port 3306 (MySQL) adalah temuan: database aplikasi cukup mendengarkan di 127.0.0.1 karena hanya dipanggil dari mesin yang sama, atau dikunci firewall bila memang diakses lintas mesin. Di daftar service, tiga yang terakhir adalah temuan khas server yang diinstal dari image desktop: cups, avahi-daemon, dan rpcbind tidak punya pengguna di server web, tetapi masing-masing berjalan dengan privilege tinggi dan menambah permukaan serangan.

audit-produksi.sh: Skor dan Rekomendasi #

Skrip dari bundle membaca ketiga snapshot, membandingkannya dengan aturan, lalu mencetak skor. Polanya mengikuti cek-maintenance.sh dari Chapter Server Maintenance : laporan adalah informasi, bukan alarm:

bash
bash audit-produksi.sh

Output yang Dihasilkan:

text
== Audit Hardening Produksi ==
[CATAT] SSH: PermitRootLogin no
[CATAT] SSH: PasswordAuthentication no
[OK]    SSH: PubkeyAuthentication yes
[CATAT] SSH: MaxAuthTries 6 (harap <= 4)
[CATAT] Port di luar rencana: 3306
[CATAT] Service tidak terpakai: cups avahi-daemon rpcbind
Skor: 1/6 lolos
Rekomendasi: perbaiki semua [CATAT] sebelum server menghadap internet.

Mari kita bedah bagian penting skripnya:

  1. cek() { ... }: fungsi pencetak hasil — argumen kedua adalah status (0 berarti lolos). printf '[%-4s]' merapikan kolom OK dan CATAT agar sejajar.
  2. grep -q '^PermitRootLogin no' sshd_config.audit lalu cek ... $?: grep -q hanya menghasilkan exit code — ketemu (0) atau tidak (1) — dan $? menangkapnya sebelum dioper ke cek. Inilah pola exit code sebagai data dari Chapter Exit Codes .
  3. awk '/^MaxAuthTries/ {print $2}': mengambil nilai 6 di samping directive, lalu dibandingkan numerik dengan [ ... -le 4 ] — audit directive bernilai butuh membaca angkanya, bukan sekadar ada-tidaknya.
  4. awk '{split($2, a, ":"); ...}': memecah kolom kedua 0.0.0.0:3306 menjadi alamat dan port; port di luar daftar rencana dikumpulkan sebagai temuan.
  5. Skor akhir: Skor: 1/6 lolos — laporan ini jujur: server ini belum siap menghadap internet. Skrip selalu exit 0 karena tugasnya melapor, bukan menjerit; perbaikan adalah pekerjaan kita membaca kolom [CATAT].

Membaca hasilnya sebagai daftar kerja: ubah kedua directive SSH pertama, turunkan MaxAuthTries, batasi MySQL ke 127.0.0.1 atau tutup portnya di firewall, lalu systemctl disable --now cups avahi-daemon rpcbind — setelahnya skor naik, dan jalankan audit lagi untuk membuktikannya.

Patch Otomatis dan Audit Berkelanjutan #

Hardening bukan tugas sekali jalan. Konfigurasi yang aman hari ini lapuk oleh dua hal: celah baru di perangkat lunak lama, dan perubahan yang diam-diam melonggarkan aturan. Untuk yang pertama, paket unattended-upgrades di Debian memasang pembaruan dari repo security secara otomatis dan bisa dikonfigurasi untuk reboot terjadwal bila service perlu dihidupkan ulang — mekanisme lengkapnya ada di halaman manualnya. Untuk yang kedua, audit produksi dijadwalkan berkala lewat cron atau systemd timer dari Chapter systemd timers , dan auditd merekam syscall sensitif (perubahan file konfigurasi, eksekusi binary tak dikenal) sebagai jejak forensik — diperkenalkan di Chapter Audit Logs .

Standar industri untuk daftar periksanya adalah CIS Benchmark: dokumen ratusan halaman per platform, diurutkan level 1 (aman tanpa mengganggu) dan level 2 (lebih ketat, berisiko memutus kompatibilitas). Checklist kita di atas adalah potongan kecilnya yang berdampak paling besar; tim yang serius menambahkan pemindai seperti itu ke pipeline provisioning, sehingga setiap server baru lahir dalam keadaan lulus audit.

Health check dari Chapter Uptime dan Health Checks memberi kita sinyal saat layanan tumbang, dan log terpusat menjelaskan alasannya. Setelah pintu terkunci, satu wilayah terakhir belum tersentuh: bagaimana manusia mengoperasikan semua ini setiap hari — runbook, on-call, insiden, dan postmortem yang tidak menyalahkan siapa pun.

Referensi lanjut: halaman manual sshd_config untuk seluruh directive SSH, serta unattended-upgrade untuk pembaruan keamanan otomatis Debian.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

audit-produksi.sh

Unduh berkas
bash
#!/usr/bin/env bash
# audit-produksi.sh — audit mini hardening produksi dari berkas snapshot.
# Di server sungguhan, snapshot diambil dari sshd_config, ss, dan systemctl;
# di latihan, nilainya dibekukan agar hasilnya selalu bisa dibandingkan.

ok=0
gagal=0

cek() {
    # $1 = pesan, $2 = status: 0 lolos, selain itu temuan
    if [ "$2" -eq 0 ]; then
        printf '[OK]    %s\n' "$1"
        ok=$((ok+1))
    else
        printf '[CATAT] %s\n' "$1"
        gagal=$((gagal+1))
    fi
}

echo "== Audit Hardening Produksi =="

# 1. SSH: tanpa root, tanpa password, kunci saja
grep -q '^PermitRootLogin no' sshd_config.audit
cek "SSH: PermitRootLogin no" $?
grep -q '^PasswordAuthentication no' sshd_config.audit
cek "SSH: PasswordAuthentication no" $?
grep -q '^PubkeyAuthentication yes' sshd_config.audit
cek "SSH: PubkeyAuthentication yes" $?

# 2. SSH: batasi percobaan gagal
maks_coba=$(awk '/^MaxAuthTries/ {print $2}' sshd_config.audit)
if [ "${maks_coba:-0}" -le 4 ]; then
    cek "SSH: MaxAuthTries ${maks_coba} (<= 4)" 0
else
    cek "SSH: MaxAuthTries ${maks_coba} (harap <= 4)" 1
fi

# 3. Hanya port layanan yang direncanakan
tak_rencana=$(awk '{split($2, a, ":"); if (a[2] != 22 && a[2] != 80 && a[2] != 443) print a[2]}' port-terbuka.txt | tr '\n' ' ')
if [ -z "$tak_rencana" ]; then
    cek "Port terbuka sesuai rencana (22, 80, 443)" 0
else
    cek "Port di luar rencana: ${tak_rencana}" 1
fi

# 4. Service yang tidak terpakai dimatikan
nganggur=$(grep -E '^(cups|avahi-daemon|rpcbind)' service-aktif.txt | cut -d. -f1 | tr '\n' ' ')
if [ -z "$nganggur" ]; then
    cek "Tidak ada service tidak terpakai" 0
else
    cek "Service tidak terpakai: ${nganggur}" 1
fi

total=$((ok+gagal))
echo "Skor: $ok/$total lolos"
if [ "$gagal" -gt 0 ]; then
    echo "Rekomendasi: perbaiki semua [CATAT] sebelum server menghadap internet."
fi