SSH Security #
Secure Shell (SSH) merupakan protokol transport utama yang digunakan oleh Ansible untuk berkomunikasi dan mengeksekusi perintah di seluruh managed node. Keamanan seluruh fleet server kita sangat bergantung pada seberapa aman konfigurasi SSH yang kita terapkan. Jika konfigurasi SSH pada server target lemah, maka seluruh enkripsi playbook dan kontrol akses yang kita bangun di control node menjadi tidak berarti. Attacker dapat mengeksploitasi protokol kriptografi yang usang, melakukan brute force pada password, atau memanfaatkan SSH key yang tidak terproteksi dengan baik untuk mengambil alih infrastruktur kita.
Artikel ini membahas secara mendalam bagaimana kita menerapkan hardening SSH secara otomatis menggunakan Ansible. Kita akan mengonfigurasi sshd_config agar hanya menerima cipher dan algoritma pertukaran kunci modern, mengelola distribusi SSH key (berbasis algoritma Ed25519 dengan passphrase kuat), membatasi akses SSH berdasarkan IP source, mengintegrasikan autentikasi dua faktor (2FA) di tingkat modul PAM, serta membangun pengawasan log audit untuk mendeteksi upaya akses ilegal sejak dini.
Kapan SSH Hardening Dibutuhkan? #
Hardening SSH harus kita lakukan sebagai bagian dari baseline standardisasi setiap server baru yang masuk ke dalam inventory kita. Namun, intensitas perlindungan dapat kita sesuaikan berdasarkan klasifikasi lingkungan server tersebut:
LINGKUNGAN YANG WAJIB HARDENING KETAT:
✓ Server produksi yang dapat diakses langsung dari internet (public IP)
✓ Server internal yang mengelola data sensitif (database, internal core API)
✓ Jump host atau Bastion server (karena merupakan pintu masuk tunggal ke internal network)
✓ Server staging dan development yang mereplikasi data customer
LINGKUNGAN DENGAN HARDENING STANDAR (Cukup nonaktifkan password auth):
✗ VM pengujian lokal / ephemeral sandbox yang akan dihancurkan dalam hitungan jam
✗ Container transient di dalam lingkungan CI/CD yang terisolasi penuh
Biaya operasional untuk menerapkan hardening SSH terotomasi sangatlah rendah karena kita hanya perlu menjalankan playbook Ansible sekali di awal. Sebaliknya, dampak pencegahannya sangatlah tinggi karena mampu menangkal lebih dari 90% serangan scanning otomatis yang terus-menerus mencari celah pada port SSH standar di internet.
Arsitektur SSH di Ansible #
Untuk merancang pengamanan yang efektif, kita harus memetakan aliran koneksi SSH dari control node hingga ke tingkat pencatatan log pada SIEM (Security Information and Event Management). Diagram di bawah ini menggambarkan arsitektur koneksi SSH tersebut:
flowchart LR
A["Ansible Control Node<br/>+ ssh-agent"] -->|"SSH key<br/>passphrase di agent"| B["Managed Node<br/>sshd"]
B -->|"Authorized key check"| C{"Key valid?"}
C -- "Ya" --> D["Login sebagai user<br/>mis. ansible"]
C -- "Tidak" --> E["Reject"]
D --> F["Privilege via<br/>sudo / become"]
B -->|"Log ke"| G["syslog / journal<br/>+ auditd"]
G -->|"Forward ke"| H["SIEM / Loki"]
style A stroke:#4a90e2,stroke-width:2px
style B stroke:#7b68ee,stroke-width:2px
style C stroke:#f5a623,stroke-width:2px
style D stroke:#50c878,stroke-width:2px
style E stroke:#d0021b,stroke-width:2px
Pada alur di atas, kita mengamankan setiap tahapan koneksi:
- At-Rest: Private key di control node dienkripsi menggunakan passphrase kuat, dan hanya didekripsi sementara di memori proses
ssh-agent. - In-Transit: Koneksi diverifikasi menggunakan authorized_keys pada managed node menggunakan algoritma asimetris modern.
- At-Run: Akun SSH default tidak boleh memiliki akses root langsung; eskalasi hak akses harus menggunakan mekanisme
become(sudo) yang diaudit secara terpisah. - Post-Run: Setiap koneksi dan aktivitas perintah dicatat secara real-time oleh
syslogdanauditd, lalu diteruskan ke server log terpusat.
Konfigurasi sshd_config yang Aman #
Secara default, konfigurasi OpenSSH di berbagai distribusi Linux masih mempertahankan kompatibilitas dengan sistem lama, yang berarti mereka masih mengizinkan enkripsi lemah yang rentan terhadap serangan decrypt modern. Kita harus mengganti konfigurasi bawaan tersebut dengan template yang aman melalui Ansible.
Template Konfigurasi sshd_config yang Direkomendasikan #
Kita bisa membuat template sshd_config.j2 yang menetapkan standar kriptografi tinggi:
# roles/ssh-hardening/templates/sshd_config.j2
# SSH Server Configuration - Hardened by Ansible
# Manual modifications will be overwritten during the next run.
Port {{ ssh_custom_port | default(22) }}
Protocol 2
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# Batasi waktu login dan jumlah percobaan
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
# Larang login root langsung dan matikan password authentication
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
UsePAM yes
# Penegakan Kriptografi Modern (Hanya Cipher dan KEX kuat)
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
# Pengaturan Timeout Sesi Tidak Aktif
ClientAliveInterval 300
ClientAliveCountMax 2
# Pembatasan Koneksi Brute Force di Tingkat Daemon
MaxStartups 10:30:100
# Pengaturan Log Audit Level Tinggi
SyslogFacility AUTH
LogLevel VERBOSE
Implementasi Playbook Hardening dan Validasi #
Ketika mengonfigurasi ulang SSH daemon, ada risiko fatal kita kehilangan koneksi ke server jika ada kesalahan sintaksis pada file sshd_config. Oleh karena itu, kita wajib melakukan validasi konfigurasi menggunakan command sshd -t sebelum melakukan restart pada service.
- name: Hardening daemon OpenSSH
hosts: all
become: true
tasks:
- name: Deploy sshd_config dari template
template:
src: templates/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
register: sshd_config_status
- name: Validasi sintaksis file sshd_config
command: /usr/sbin/sshd -t
changed_when: false
register: sshd_syntax_check
when: sshd_config_status.changed
- name: Restart service SSH daemon
service:
name: sshd
state: restarted
when:
- sshd_config_status.changed
- sshd_syntax_check.rc == 0
sshd -t akan menganalisis seluruh file konfigurasi dan melaporkan jika ada opsi yang tidak didukung atau kesalahan ketik. Task restart hanya akan dijalankan jika pengecekan tersebut mengembalikan kode keluar 0 (sukses), memastikan kita tidak pernah terkunci dari server akibat kesalahan konfigurasi.
Distribusi SSH Key yang Aman #
Menggunakan SSH key adalah langkah awal yang sangat baik, namun jika kita tidak mengelola siklus hidup key tersebut dengan benar, sistem kita tetap rentan. Kita harus menghindari penggunaan satu SSH key yang sama tanpa passphrase untuk semua administrator.
Generate SSH Key dengan Proteksi Tinggi #
Saat membuat key pair baru di control node kita, pastikan kita menggunakan algoritma Ed25519 yang efisien dan aman, serta menambahkan parameter round KDF yang tinggi untuk mempersulit serangan brute force offline jika file private key tersebut bocor:
# Men-generate SSH key modern berbasis Ed25519
ssh-keygen -t ed25519 -a 100 -C "ansible-admin-production" -f ~/.ssh/ansible_ed25519
Playbook Distribusi Key Menggunakan Ansible #
Kita bisa mendistribusikan public key baru ke seluruh managed node secara efisien menggunakan modul authorized_key di Ansible. Kita juga bisa mengaktifkan mode exclusive untuk menghapus semua key lain yang tidak terdaftar secara eksplisit di playbook kita:
- name: Distribusi SSH public key secara eksklusif
hosts: servers
become: true
vars:
ansible_ssh_user: deployer
tasks:
- name: Pastikan direktori .ssh memiliki permission yang aman
file:
path: "/home/{{ ansible_ssh_user }}/.ssh"
state: directory
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
mode: '0700'
- name: Daftarkan public key baru dan bersihkan key tidak dikenal
authorized_key:
user: "{{ ansible_ssh_user }}"
state: present
key: "{{ lookup('file', '~/.ssh/ansible_ed25519.pub') }}"
exclusive: true # Menghapus key lain yang tidak terdaftar di sini
register: key_push_result
- name: Catat log audit distribusi key baru
lineinfile:
path: /var/log/ansible-key-audit.log
line: "[{{ ansible_date_time.iso8601 }}] Key distributed to {{ inventory_hostname }} for user {{ ansible_ssh_user }}"
create: true
owner: root
group: root
mode: '0600'
when: key_push_result.changed
Opsiexclusive: trueakan menghapus seluruh key lain dari fileauthorized_keys. Pastikan kita telah memasukkan seluruh public key developer yang sah sebelum mengaktifkan opsi ini, jika tidak, mereka akan kehilangan akses ke server target secara instan.
Pembatasan Akses Berdasarkan IP #
Membatasi akses SSH di tingkat jaringan adalah salah satu langkah mitigasi paling efektif terhadap serangan brute force. Jika server kita hanya membutuhkan akses dari VPN internal atau jump host khusus, kita harus menutup akses port SSH dari internet publik.
Konfigurasi Firewall via Playbook Ansible #
Kita bisa memanfaatkan modul iptables di Ansible untuk mengotomasi aturan firewall ini secara konsisten:
- name: Konfigurasi pembatasan IP firewall untuk port SSH
hosts: production_nodes
become: true
vars:
allowed_subnets:
- 10.10.0.0/16 # VPN subnet internal kita
- 192.168.100.50 # IP statis Bastion host kita
tasks:
- name: Izinkan koneksi SSH dari subnet terdaftar
iptables:
chain: INPUT
protocol: tcp
destination_port: 22
source: "{{ item }}"
jump: ACCEPT
comment: "Allow SSH from trusted network"
loop: "{{ allowed_subnets }}"
- name: Tolak akses SSH dari IP lainnya
iptables:
chain: INPUT
protocol: tcp
destination_port: 22
jump: DROP
comment: "Drop all other SSH traffic"
Implementasi Fail2ban untuk Mengatasi Spam Scanner #
Jika server kita terpaksa harus terekspos ke internet publik, kita harus menginstal fail2ban untuk memblokir IP secara otomatis jika mereka terdeteksi melakukan kesalahan autentikasi berulang kali:
- name: Instalasi dan konfigurasi fail2ban
hosts: public_servers
become: true
tasks:
- name: Install paket fail2ban
package:
name: fail2ban
state: present
- name: Terapkan konfigurasi jail kustom untuk SSH
copy:
dest: /etc/fail2ban/jail.d/ssh-jail.local
content: |
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
owner: root
group: root
mode: '0644'
register: fail2ban_config
- name: Pastikan fail2ban aktif dan berjalan
service:
name: fail2ban
state: restarted
enabled: true
when: fail2ban_config.changed
Two-Factor Authentication dengan PAM #
Untuk server produksi yang menampung data paling sensitif, kita disarankan untuk menambahkan autentikasi dua faktor (2FA) di atas SSH key. Hal ini memastikan bahwa jika laptop developer hilang atau key privat mereka dicuri, pelaku tetap tidak dapat masuk tanpa kode TOTP yang dinamis.
Integrasi Google Authenticator dengan PAM #
Kita bisa menggunakan modul Google Authenticator PAM (Pluggable Authentication Modules) di Linux untuk mengotomasi proses ini:
- name: Konfigurasi Multi-Factor Authentication untuk SSH
hosts: secure_nodes
become: true
tasks:
- name: Install modul PAM Google Authenticator
package:
name: libpam-google-authenticator
state: present
- name: Aktifkan modul PAM di konfigurasi SSH
lineinfile:
path: /etc/pam.d/sshd
line: "auth required pam_google_authenticator.so nullok"
state: present
- name: Sesuaikan sshd_config untuk memaksa 2FA
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^ChallengeResponseAuthentication"
line: "ChallengeResponseAuthentication yes"
register: sshd_pam_update
- name: Restart SSH service jika konfigurasi berubah
service:
name: sshd
state: restarted
when: sshd_pam_update.changed
Ketika user pertama kali login, mereka harus menjalankan perintah google-authenticator di shell mereka untuk memindai QR code dan menyimpan recovery keys. Parameter nullok pada modul PAM di atas memastikan bahwa user baru masih dapat login untuk mengonfigurasi 2FA mereka pertama kali sebelum sistem sepenuhnya mewajibkan kode TOTP pada login berikutnya.
Audit Koneksi SSH #
Pencegahan hanya merupakan setengah dari strategi keamanan. Setengah lainnya adalah kemampuan kita untuk memantau, mendeteksi, dan merespons anomali. Kita harus mengonfigurasi logging SSH agar mencatat setiap detail pertukaran kunci dan memantau integritas file kunci menggunakan daemon audit (auditd).
Setup Pengawasan Integritas dengan Auditd #
Kita bisa menyebarkan konfigurasi auditd untuk melacak perubahan ilegal pada direktori .ssh dan file sshd_config:
- name: Konfigurasi log auditd untuk SSH
hosts: all
become: true
tasks:
- name: Install paket auditd
package:
name: auditd
state: present
- name: Deploy rules pengawasan file SSH
copy:
dest: /etc/audit/rules.d/ssh-audit.rules
content: |
# Monitor perubahan konfigurasi daemon SSH
-w /etc/ssh/sshd_config -p wa -k ssh_config_change
# Monitor perubahan kunci host server
-w /etc/ssh/ssh_host_ed25519_key -p wa -k ssh_hostkey_change
# Monitor manipulasi file authorized_keys user
-w /home/deployer/.ssh/authorized_keys -p wa -k ssh_authkeys_change
owner: root
group: root
mode: '0600'
register: auditd_rules
- name: Restart daemon auditd
service:
name: auditd
state: restarted
when: auditd_rules.changed
Dengan mengaktifkan rules ini, jika ada penyusup yang berhasil masuk dan mencoba menambahkan backdoor SSH key ke file authorized_keys deployer secara manual bypass Ansible, daemon auditd akan mencatat event tersebut secara instan. Log ini kemudian dapat dibaca oleh monitoring agent untuk memicu alert dengan tingkat keparahan tinggi di dashboard SOC kita.
SSH Agent Forwarding — Risiko dan Mitigasi #
SSH Agent Forwarding adalah fitur yang sangat memudahkan kita untuk menggunakan SSH key lokal kita pada koneksi berikutnya yang diinisiasi dari server target. Namun, fitur ini memiliki risiko keamanan yang sangat besar. Jika server target yang kita hubungi telah dikompromikan oleh attacker, mereka dapat mengakses soket ssh-agent kita di server tersebut untuk melakukan autentikasi ke server target lainnya seolah-olah mereka adalah kita.
SKENARIO SERANGAN AGENT FORWARDING:
Control Node (Kita) --(ssh -A)--> Server Target A (Compromised!) --(Akses Socket)--> Server B
Attacker di Server A yang memiliki akses root dapat membajak koneksi socket SSH agent kita
untuk masuk ke Server B tanpa perlu mengetahui private key kita.
Mitigasi Menggunakan ProxyJump (Alternatif Aman) #
Untuk menghindari risiko ini, kita harus mematikan ForwardAgent secara global di konfigurasi SSH client kita, dan beralih menggunakan fitur ProxyJump. Dengan ProxyJump, koneksi SSH dari control node ke server tujuan di-proxy melalui jump host di tingkat TCP, tanpa pernah mengekspos socket ssh-agent kita ke jump host tersebut.
Kita bisa mengonfigurasi SSH client di control node kita secara otomatis melalui Ansible:
- name: Konfigurasi SSH client yang aman di control node
hosts: localhost
connection: local
tasks:
- name: Pastikan file ~/.ssh/config terkonfigurasi dengan aman
copy:
dest: "~/.ssh/config"
content: |
# Default: Matikan agent forwarding secara global
Host *
ForwardAgent no
StrictHostKeyChecking yes
ServerAliveInterval 120
HashKnownHosts yes
# Gunakan ProxyJump untuk mengakses jaringan internal produksi
Host 10.20.*
ProxyJump bastion.company.com
IdentityFile ~/.ssh/ansible_ed25519
owner: "{{ ansible_env.USER }}"
mode: '0600'
Decision Tree — Pilih Strategi Hardening #
Untuk membantu kita menentukan strategi hardening yang tepat sesuai dengan postur eksposur jaringan server kita, kita bisa merujuk pada decision tree berikut:
flowchart TD
A["Server menerima<br/>koneksi SSH?"] --> B{"Exposure"}
B -- "Internet-facing<br/>public IP" --> C["Aggressive hardening<br/>+ 2FA + fail2ban<br/>+ non-standard port"]
B -- "Internal subnet<br/>private IP" --> D["Standard hardening<br/>+ SSH key only<br/>+ auditd"]
B -- "Behind bastion<br/>jump host only" --> E["Strict hardening<br/>+ ProxyJump<br/>+ no direct SSH"]
C --> F{"Butuh 2FA<br/>untuk user?"}
F -- "Ya" --> G["Google Authenticator<br/>atau DUO"]
F -- "Tidak" --> H["SSH key only<br/>dari CI/CD"]
D --> I["Distribusikan key<br/>via Ansible"]
E --> I
style A stroke:#4a90e2,stroke-width:2px
style B stroke:#7b68ee,stroke-width:2px
style C stroke:#d0021b,stroke-width:2px
style D stroke:#f5a623,stroke-width:2px
style E stroke:#50c878,stroke-width:2px
Dengan mengikuti bagan keputusan ini, kita dapat menetapkan tingkat pengamanan yang optimal tanpa memberikan hambatan (friction) operasional yang tidak perlu pada lingkungan development non-sensitif kita.
Ringkasan #
- Bukan Root Login — Nonaktifkan izin root login langsung (
PermitRootLogin no) disshd_configuntuk memastikan setiap aktivitas dapat dilacak kembali ke user individu yang sah.- Tanpa Password Authentication — Selalu gunakan
PasswordAuthentication no. Kredensial berbasis password sangat rentan terhadap brute force scanner otomatis.- Algoritma Modern — Batasi cipher dan KEX algorithms hanya pada standar modern yang aman (seperti
curve25519-sha256dan[email protected]).- Validasi Sintaksis — Selalu jalankan validasi
/usr/sbin/sshd -tsebelum melakukan restart pada service SSH agar kita tidak terkunci akibat kesalahan ketik konfigurasi.- Passphrase untuk Key — Gunakan private key dengan algoritma Ed25519 yang dilindungi oleh passphrase kuat. Hindari penggunaan SSH key tanpa passphrase di lingkungan produksi.
- Firewall di Port SSH — Batasi source IP yang diizinkan menghubungi port SSH kita hanya dari segmen IP VPN internal atau bastion host tepercaya.
- Autentikasi Dua Faktor — Integrasikan PAM Google Authenticator untuk memberikan perlindungan berlapis (TOTP) bagi administrator manusia yang mengakses server target.
- Gunakan ProxyJump — Nonaktifkan agent forwarding secara global untuk mencegah pembajakan socket agent oleh admin server target yang dikompromikan, gunakan
ProxyJumpsebagai gantinya.- Audit File Kunci — Pasang rules
auditduntuk memantau perubahan tidak sah pada fileauthorized_keysdan file konfigurasi SSH secara real-time.
← Sebelumnya: Secret Management Berikutnya: Common Mistake →