SSH Security

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:

  1. At-Rest: Private key di control node dienkripsi menggunakan passphrase kuat, dan hanya didekripsi sementara di memori proses ssh-agent.
  2. In-Transit: Koneksi diverifikasi menggunakan authorized_keys pada managed node menggunakan algoritma asimetris modern.
  3. At-Run: Akun SSH default tidak boleh memiliki akses root langsung; eskalasi hak akses harus menggunakan mekanisme become (sudo) yang diaudit secara terpisah.
  4. Post-Run: Setiap koneksi dan aktivitas perintah dicatat secara real-time oleh syslog dan auditd, 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
Opsi exclusive: true akan menghapus seluruh key lain dari file authorized_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) di sshd_config untuk 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-sha256 dan [email protected]).
  • Validasi Sintaksis — Selalu jalankan validasi /usr/sbin/sshd -t sebelum 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 ProxyJump sebagai gantinya.
  • Audit File Kunci — Pasang rules auditd untuk memantau perubahan tidak sah pada file authorized_keys dan file konfigurasi SSH secara real-time.

← Sebelumnya: Secret Management   Berikutnya: Common Mistake →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact