Saturday, June 20, 2015

Connection Rate Limiting with HAProxy


Salah satu fitur penting dari HAProxy adalah membatasi jumlah koneksi yang diteruskan ke backend. Dengan membatasi jumlah koneksi, HAProxy dapat membantu mengatasi DDOS yang dilakukan oleh pihak yang tidak bertanggung jawab. HAProxy melakukan perhitungan jumlah koneksi dengan memanfaatkan fitur yang bernama stick-table, yang dapat menyimpan berbagai informasi penting dari HTTP client antara lain source IP address. Dengan mencatat source IP address, HAProxy dapat memfilter IP address yang terindikasi abusive client dengan melihat jumlah koneksi yang dilakukan. Tertarik dengan fitur tersebut, saya mencoba mencari referensi bagaimana caranya untuk saya aplikasikan sendiri.

Berawal dari googling sederhana, saya menemukan sebuah artikel yang sangat bagus membahas performance tuning HAProxy dengan studi kasus StackExchange [1]. Pada artikel tersebut dijabarkan secara mendetil mulai dari arsitektur yang digunakan hingga konfigurasi baris per baris. Namun konfigurasinya sudah terlampau rumit bagi saya untuk memahaminya. Saya kemudian mencari referensi lain dan menemukan artikel lainnya yang lebih fokus terhadap topik yang sedang saya cari [2]. Masih ragu dengan pemahaman saya setelah membacanya, saya memutuskan untuk mencoba sendiri dengan melakukan sebuah simulasi sederhana.

Saat penulisan ini, saya sedang tidak memiliki akses ke mesin HAProxy development, sehingga saya hanya akan menggunakan HAProxy versi Homebrew dan Apache JMeter untuk mensimulasikan abusive client. Keduanya akan saya jalankan langsung dari laptop saya. Untuk backend saya akan menggunakan 2 dummy backend yang hanya mengembalikan response HTTP OK untuk client yang benar dan HTTP 503 Server Unavailable untuk abusive client.

Berikut adalah konfigurasi HAProxy yang digunakan:
global
    maxconn 4096
    pidfile /Users/Okky/.haproxy/run/haproxy.pid

defaults
    mode http
    timeout connect 5s
    timeout client 50s
    timeout server 50s

frontend example_frontend
    bind *:10000
    stick-table type ip size 200k expire 10m store gpc0
    acl source_is_abuser sc1_get_gpc0 gt 0
    tcp-request connection track-sc1 src if !source_is_abuser
    use_backend example_error_backend if source_is_abuser
    default_backend example_ok_backend

backend example_ok_backend
    stick-table type ip size 200k expire 30s store conn_rate(100s)
    tcp-request content track-sc2 src
    acl conn_rate_abuse sc2_conn_rate gt 5
    acl mark_as_abuser sc1_inc_gpc0 gt 0
    tcp-request content reject if conn_rate_abuse mark_as_abuser
    server dummy 127.0.0.1:9999 maxconn 50 disabled
    errorfile 503 /Users/Okky/.haproxy/response/200.http

backend example_error_backend
    server dummy 127.0.0.1:9999 maxconn 50 disabled
    errorfile 503 /Users/Okky/.haproxy/response/503.http

HTTP response 200 di-generate dengan membuat file 200.http berikut:
HTTP/1.0 200 OK
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>200 OK</h1>
Hello World!
</body></html>
 

Sedangkan response 503 di-generate dengan membuat file 503.http berikut:
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>
 

Saya akan coba menjelaskan konfigurasinya. Pada konfigurasi di atas example_frontend listen di port 10000. Frontend ini memiliki stick-table untuk menyimpan source IP address kapasitas maksimum 200.000 entry dengan masa berlaku 10 menit dan disimpan sebagai gpc0 (General Purpose Counter dari HAProxy). Pada frontend ini didefinisikan indikasi abusive client jika counter sc1 (frontend) nilainya lebih dari 0. Ketika HAProxy pertama kali dijalankan, nilai counter sc1 ini adalah 0. Nilai counter sc1 ini akan terus dimonitor selama abusive client terus melakukan koneksi. Jika terindikasi abusive client, maka akan diteruskan ke backend 503, jika tidak maka akan diteruskan ke backend 200.

Selanjutnya di example_ok_backend, secara default backend ini akan menerima request awal dengan asumsi semua client adalah client yang baik. Backend ini juga memiliki stick-table yang digunakan untuk meyimpan source IP address client berkapasitas 200.000 entry dengan masa berlaku hanya 30 detik sebagai connection rate selama durasi 100 detik. Misalnya selama 100 detik saya melakukan 2 kali perintah "wget http://localhost:10000", maka nilai counter sc2 (backend) untuk IP saya adalah 0,02. Backend ini juga akan memonitor nilai sc2 untuk setiap request yang berhasil masuk ke backend. Ketika sc2 connection rate lebih besar dari 5, maka source IP address tersebut terindikasi abusive client. Request tersebut akan ditolak oleh backend dan secara bersamaan backend akan meng-increment counter sc1 (frontend) untuk IP yang sama. Dengan demikian, untuk request selanjutnya dari source IP address yang terindikasi abusive client langsung dialihkan ke example_error_backend.

Untuk membuktikannya, saya menjalankan script Apache JMeter yang cara kerjanya akan mengakses http://localhost:10000 selama 60 detik sebanyak 100 users. Pada 5 request pertama, terlihat backend dengan senang hati mengembalikan respons HTTP 200 OK.

Namun pada request ke-6, terlihat niat tidak baik dari client, sehingga HAProxy menolak request tersebut.

Request ke-7 dan seterusnya, HAProxy sudah yakin bahwa client IP tersebut merupakan abusive client, maka HAProxy akan meneruskan ke backend error.

Sesuai konfigurasi HAProxy, stick-table pada frontend akan kadaluarsa setelah 10 menit. Saya mensimulasikannya dengan melakukan 2 kali perintah "wget http://localhost:10000" pada terminal. Perintah pertama saat sebelom Apache JMeter dijalankan, dan perintah kedua 10 menit setelah Apache JMeter dihentikan.

Simulasi sederhana ini berhasil meyakinkan saya bahwa HAProxy dapat digunakan sebagai benteng utama terhadap backend servers, sehingga serangan seperti DDOS dapat dihindari. Selamat mencoba dan semoga bermanfaat!


Daftar Referensi:

1 comment: