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:
| Item | Prioritas | Wujud di sistem |
|---|---|---|
| SSH kunci saja, tanpa root | kritis | PermitRootLogin no, PasswordAuthentication no |
| Firewall default deny | kritis | hanya port layanan (22, 80, 443) yang terbuka |
| Patch keamanan otomatis | kritis | unattended-upgrades |
| Service minimum | tinggi | matikan cups, avahi-daemon, rpcbind |
| Aplikasi tanpa root | tinggi | nginx sebagai www-data, aplikasi punya user sendiri |
| Audit trail | tinggi | auditd merekam syscall sensitif |
| Backup terverifikasi | tinggi | uji restore berkala, bukan hanya job berjalan |
| TLS di semua jalur | tinggi | rujukan Chapter TLS dan SSL Certificates |
| Secrets terkelola | tinggi | rujukan 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:
cat sshd_config.auditOutput yang Dihasilkan:
# Snapshot /etc/ssh/sshd_config untuk audit — nilai sengaja belum aman
Port 22
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
MaxAuthTries 6
X11Forwarding noEnam 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:
cat port-terbuka.txt
echo
cat service-aktif.txtOutput yang Dihasilkan:
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.servicePort 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 audit-produksi.shOutput yang Dihasilkan:
== 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:
cek() { ... }: fungsi pencetak hasil — argumen kedua adalah status (0berarti lolos).printf '[%-4s]'merapikan kolomOKdanCATATagar sejajar.grep -q '^PermitRootLogin no' sshd_config.auditlalucek ... $?:grep -qhanya menghasilkan exit code — ketemu (0) atau tidak (1) — dan$?menangkapnya sebelum dioper kecek. Inilah pola exit code sebagai data dari Chapter Exit Codes .awk '/^MaxAuthTries/ {print $2}': mengambil nilai6di samping directive, lalu dibandingkan numerik dengan[ ... -le 4 ]— audit directive bernilai butuh membaca angkanya, bukan sekadar ada-tidaknya.awk '{split($2, a, ":"); ...}': memecah kolom kedua0.0.0.0:3306menjadi alamat dan port; port di luar daftar rencana dikumpulkan sebagai temuan.- Skor akhir:
Skor: 1/6 lolos— laporan ini jujur: server ini belum siap menghadap internet. Skrip selaluexit 0karena 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#!/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