Playbook Saya Bilang Changed, sshd Bilang Tetap Buka
Playbook hardening bilang changed, tapi sshd -T masih passwordauthentication yes: first-match-wins dan urutan Include membuat 50-cloud-init.conf menang.
Ringkasan
Hardening SSH-nya gagal karena sshd pakai aturan first-match-wins: nilai pertama yang dibaca yang menang, bukan yang terakhir. File 50-cloud-init.conf isinya PasswordAuthentication yes dan urutan alfabetnya lebih dulu daripada drop-in-ku, jadi menang terus. Solusinya ganti nama jadi 00-hardening.conf, komen baris di config utama, dan selalu verifikasi pakai sshd -T.
Playbook hardening SSH baru saja selesai di salah satu host, statusnya hijau, task terakhir bilang changed. Lalu saya jalankan sshd -T | grep -i password buat memastikan, dan di layar masih berdiri passwordauthentication yes. Tebakan pertama saya: file drop-in-nya nggak ke-tulis ke disk. Saya salah total. File-nya ke-tulis; yang salah adalah asumsi saya soal cara sshd membaca config.
First Match Menang, Sisanya Dibaca Siapa Tau Ada
OpenSSH punya aturan yang gampang dilupakan: kecuali disebutkan lain, untuk setiap keyword, nilai pertama yang diperoleh itulah yang dipakai [1]. Bukan nilai terakhir, bukan file yang paling spesifik. Begitu sshd menemukan PasswordAuthentication yes di posisi lebih awal, semua baris no setelahnya cuma jadi hiasan.
Posisi "lebih awal" itu ternyata licik. Direktif Include di sshd_config mengembangkan pola glob dan memprosesnya dalam urutan leksikal [1]. File cloud-init bernama 50-cloud-init.conf berisi PasswordAuthentication yes. Drop-in saya bernama 50-hardening.conf. Sama-sama dimuat dari Include, dan yang diurutan awal alfabet menang: c sebelum h. Bukan semangat "yang terakhir menimpa", murni first-match-wins.
Saya nggak sendirian. Di issue cloud-init #5934, pengguna Ubuntu 24.04 mengeluh 50-cloud-init.conf dengan PasswordAuthentication yes menimpa no di config utamanya, dan dia cuma menemukan filenya setelah grep PasswordAuthentication ke seluruh /etc/ssh. Issue-nya ditutup "not planned" [3]. Gejala yang persis sama, penyebab yang persis sama.
Tiga Langkah yang Akhirnya Berhasil
Drop-in saya ganti nama jadi 00-hardening.conf supaya mengalahkan semua file 50-* secara leksikal:
# /etc/ssh/sshd_config.d/00-hardening.conf
PasswordAuthentication no
PermitRootLogin prohibit-passwordNamun drop-in saja belum cukup. Kalau di sshd_config utama ada baris aktif PermitRootLogin atau PasswordAuthentication yang posisinya di atas Include, baris itu tetap menang atas semua drop-in. Playbook saya sekarang mengomentari baris seperti itu via modul lineinfile dengan opsi backrefs [4], plus menulis ulang nilai PasswordAuthentication di dalam 50-cloud-init.conf itu sendiri jadi no. Sebelum eksekusi, snapshot sshd_config.bak- diambil, dan sshd hanya di-reload, bukan restart, supaya sesi yang lagi hidup nggak putus.
Satu detail yang bikin verifikasi saya hampir salah terus: sshd -T mencetak permitrootlogin without-password, padahal playbook menulis prohibit-password. Kedua ejaan itu bermakna sama (login root cuma lewat key), jadi pengecekan idempotensi dan verifikasi menerima keduanya. Tanpa itu, playbook akan menganggap host-nya "belum harden" selamanya dan menulis ulang config tiap jalan.
Verifikasi Efektif, Bukan File yang Kita Tulis
Playbook ini manual-only: butuh -e confirm=yes, wajib --limit per host, dan nggak pernah saya sambungkan ke cron atau automation. Sesuai kebijakan repo, operasi yang mengubah state harus ada approval manusia per host. Justru karena playbook semacam ini "hanya sekali jalan", godaan buat mempercayai status hijau-nya makin besar.
Pelajaran yang saya bawa: verifikasi config efektifnya, bukan file yang kita tulis. sshd -T mencetak hasil akhir seluruh lapisan config setelah aturan first-match dan urutan Include dipakai. File yang ditulis dengan susah payah nggak ada artinya kalau daemon membacanya dengan aturan prioritas yang kita abaikan. Sekarang, setiap playbook yang menyentuh sshd diakhiri dengan pengecekan sshd -T, bukan sekadar recap changed=1. Kecil buat ditulis, besar efeknya pas insiden beneran terjadi.
Sources
[3] cloud-init issue #5934: Cannot disable PasswordAuthentication due to 50-cloud-init.conf