Sunday, September 29, 2024

Chặn truy cập vào webserver sử dụng HAProxy

-

1. Tổng quan.

HAProxy là một máy chủ cân bằng tải nguồn mở phổ biến và mạnh mẽ, thường được sử dụng để cải thiện hiệu suất và độ tin cậy của máy chủ web. Nó cung cấp nhiều tính năng, bao gồm khả năng chặn hoặc hạn chế truy cập từ các IP hoặc quốc gia cụ thể.

Có một số lý do mà bạn có thể muốn sử dụng HAProxy để chặn truy cập từ một số quốc gia cụ thể:

  • Bảo mật: Nếu bạn nhận thấy một lượng lớn hoạt động đáng ngờ hoặc tấn công từ một quốc gia cụ thể, bạn có thể muốn chặn truy cập từ quốc gia đó để bảo vệ hệ thống của bạn.
  • Tuân thủ pháp luật: Một số quốc gia có luật đặt ra các hạn chế về loại dữ liệu có thể được chia sẻ với người dùng ở quốc gia khác. Trong trường hợp này, bạn có thể cần chặn truy cập từ những quốc gia này để tuân thủ pháp luật.
  • Hiệu suất: Nếu bạn chỉ muốn phục vụ người dùng ở một số quốc gia cụ thể, việc chặn truy cập từ những quốc gia khác có thể giúp giảm bớt tải trên máy chủ của bạn và cải thiện hiệu suất cho những người dùng mà bạn muốn phục vụ.

Lưu ý rằng việc chặn truy cập dựa trên quốc gia có thể không hoàn toàn chính xác, vì IP không phải lúc nào cũng phản ánh chính xác vị trí địa lý của người dùng.

2. Thực hành.

Để cài đặt một môi trường thử nghiệm chạy Ubuntu, bao gồm webserver (ví dụ: Apache), HAProxy, bạn có thể thực hiện theo các bước sau:

  • Cài đặt Ubuntu trên máy ảo hoặc máy chủ vật lý.
  • Cài đặt Apache (webserver).
  • Cài đặt HAProxy.

Bước 1: Cài đặt Ubuntu

Bạn có thể cài đặt Ubuntu trên máy ảo (ví dụ: sử dụng VirtualBox hoặc VMWare) hoặc trên máy chủ vật lý. Bạn có thể tải xuống ISO của Ubuntu từ trang web chính thức và làm theo hướng dẫn cài đặt.

Bước 2: Cài đặt Apache

Mở Terminal và chạy các lệnh sau để cài đặt Apache:

sudo apt update
sudo apt install apache2

Bước 3: Cài đặt HAProxy

Chạy các lệnh sau để cài đặt HAProxy:

sudo apt update
sudo apt install haproxy

Bước 4: Đổi port mặc định 80 của Apache.

Nếu Apache và HAProxy cùng lắng nghe trên cùng một cổng (ví dụ: cổng 80), sẽ xảy ra xung đột. Để giải quyết vấn đề này, bạn có thể cấu hình Apache lắng nghe trên một cổng khác, ví dụ cổng 8080.

Đầu tiên, cấu hình Apache để lắng nghe trên cổng 8080. Chỉnh sửa file cấu hình Apache, thường là /etc/apache2/ports.conf:

sudo vi /etc/apache2/ports.conf

Thay đổi Listen 80 thành Listen 8080.

# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 8080

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

Khởi động lại Apache:

sudo service apache2 restart

Bước 5: Chuyển tiếp yêu cầu đến cổng 8080 của Apache.

Tiếp theo, cấu hình HAProxy để chuyển tiếp yêu cầu đến cổng 8080 của Apache. Chỉnh sửa file cấu hình HAProxy, thường là /etc/haproxy/haproxy.cfg:

sudo vi /etc/haproxy/haproxy.cfg

Sau khi cài đặt, bạn cần cấu hình HAProxy để chuyển tiếp yêu cầu đến Apache. Bạn có thể chỉnh sửa file cấu hình HAProxy (/etc/haproxy/haproxy.cfg) và thêm cấu hình tương tự như sau:

frontend http_front
   bind *:80
   stats uri /haproxy?stats
   default_backend http_back

backend http_back
   balance roundrobin
   server webserver localhost:8080 check

Cuối cùng, khởi động lại HAProxy:

sudo service haproxy restart

Trong cấu hình HAProxy, phần frontend http_front định nghĩa một frontend tên là http_front chấp nhận kết nối trên tất cả các địa chỉ IP (*) tại cổng 80. Đường dẫn /haproxy?stats được sử dụng để hiển thị thống kê HAProxy.

Phần backend http_back định nghĩa một backend tên là http_back. Trong backend này, có một máy chủ (được gọi là webserver) tại localhost:8080localhost:8080 là địa chỉ của webserver Apache.

balance roundrobin nghĩa là HAProxy sẽ phân phối yêu cầu đến các máy chủ theo lượt, trong trường hợp này chỉ có một máy chủ là Apache.

default_backend http_back trong phần frontend nghĩa là tất cả các yêu cầu đến frontend http_front sẽ được chuyển tiếp đến backend http_back, tức là chúng sẽ được chuyển tiếp đến webserver Apache.

Bước 6: Cài đặt và cấu hình GeoIP.

Để chặn các IP từ một quốc gia nào đó truy cập vào webserver của bạn bằng HAProxy và GeoIP, bạn cần thực hiện các bước sau:

  • Cài đặt GeoIP trên máy chủ HAProxy của bạn.
  • Tải xuống và cập nhật cơ sở dữ liệu GeoIP.
  • Cấu hình HAProxy để sử dụng GeoIP.

Dưới đây là một ví dụ về cách cấu hình HAProxy để chặn tất cả các yêu cầu từ một quốc gia cụ thể (ví dụ: CN cho Trung Quốc).

Cài đặt GeoIP.

sudo apt-get install geoip-bin

Tải xuống cơ sở dữ liệu GeoIP.

wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
gunzip GeoIP.dat.gz
sudo mv GeoIP.dat /usr/share/GeoIP/

Thay đổi cấu hình file vi /etc/haproxy/haproxy.cfg:

frontend http_front
   bind *:80
   acl blocked_country src -m geoip /usr/share/GeoIP/GeoIP.dat CN
   http-request deny if blocked_country
   default_backend http_back

backend http_back
   balance roundrobin
   server webserver localhost:8080 check

Trong đoạn cấu hình trên, src -m geoip /usr/share/GeoIP/GeoIP.dat CN sẽ kiểm tra xem IP nguồn có phải từ Trung Quốc không. Nếu đúng, yêu cầu sẽ bị từ chối.

Cuối cùng, khởi động lại HAProxy:

sudo service haproxy restart

Và đây là kết quả nếu sử dụng IP có mã quốc gia là CN truy cập vào webserver.

3. GeoLite2 với HAProxy (cập nhật mới cho ai sử dụng GeoIP từ nằm tháng 1/2019).

MaxMind đã ngừng hỗ trợ dịch vụ GeoLite Legacy từ ngày 2 tháng 1 năm 2019. Điều này có nghĩa là bạn không thể tải xuống cơ sở dữ liệu GeoIP từ geolite.maxmind.com nữa.

Tuy nhiên, MaxMind vẫn cung cấp dịch vụ GeoLite2, một phiên bản cập nhật của GeoLite, nhưng yêu cầu bạn phải đăng ký một tài khoản miễn phí để tải xuống cơ sở dữ liệu.

Bạn có thể đăng ký tài khoản và tải xuống cơ sở dữ liệu GeoLite2 từ trang web của MaxMind. Sau khi tải xuống, bạn sẽ cần sử dụng một công cụ như geoipupdate hoặc mmdblookup từ gói libmaxminddb để truy vấn cơ sở dữ liệu.

Lưu ý rằng HAProxy không hỗ trợ trực tiếp GeoLite2. Bạn sẽ cần sử dụng một công cụ như mmdblookup để truy vấn cơ sở dữ liệu và sau đó sử dụng kết quả trong cấu hình HAProxy của mình.

Để sử dụng GeoLite2 với HAProxy, bạn cần sử dụng một công cụ như mmdblookup để truy vấn cơ sở dữ liệu GeoIP. Tuy nhiên, HAProxy không hỗ trợ trực tiếp việc này, vì vậy bạn cần sử dụng một script để thực hiện việc truy vấn và sau đó trả về kết quả cho HAProxy.

Dưới đây là một ví dụ về cách làm điều này:

Bạn có thể sử dụng thư viện lua-maxminddb để truy vấn dữ liệu GeoIP từ Lua. Đầu tiên, bạn cần cài đặt thư viện này. Bạn có thể cài đặt nó bằng lệnh luarocks install lua-maxminddb.

sudo apt-get install libmaxminddb0 libmaxminddb-dev mmdb-bin lua-maxminddb -y

Tải xuống cơ sở dữ liệu GeoLite2 từ trang web của MaxMind. Bạn sẽ cần đăng ký một tài khoản miễn phí để tải xuống cơ sở dữ liệu.

Giải nén cơ sở dữ liệu và di chuyển nó vào thư mục /usr/share/GeoIP/:

gunzip GeoLite2-Country.mmdb.gz
sudo mv GeoLite2-Country.mmdb /usr/share/GeoIP/

Tạo một script để truy vấn cơ sở dữ liệu. Script này sẽ nhận IP từ HAProxy, truy vấn cơ sở dữ liệu GeoIP và trả về mã quốc gia:

cat > /home/ipinfo.lua << 'OEF'
local maxminddb = require("maxminddb")
local db = maxminddb.open('/usr/share/GeoIP/GeoLite2-Country.mmdb')

function get_country(ip)
    local res = db:lookup(ip)
    if res and res.country then
        return res.country.iso_code
    else
        return nil
    end
end
OEF

Trong đoạn code trên, hàm get_country nhận một địa chỉ IP và trả về mã quốc gia tương ứng. Bạn có thể sử dụng hàm này trong cấu hình HAProxy của mình để đặt giá trị của txn.country:

Bạn cũng có thể tạo một trang nội dung cảnh báo như sau:

cat > /home/403.html << 'OEF'
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
    <title>403 Forbidden</title>
</head>
<body>
    <h1>403 Forbidden</h1>
    <p>Request forbidden by administrative rules. You are accessing from country: "CN" (China) ()</p>
</body>
</html>
OEF

Cấu hình HAProxy để sử dụng script này. Trong file cấu hình HAProxy (/etc/haproxy/haproxy.cfg), bạn có thể sử dụng lệnh http-request deny để từ chối các yêu cầu từ một quốc gia cụ thể:

cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
    lua-load /home/ipinfo.lua

defaults
    mode http
    errorfile 403 /home/403.html

frontend http_front
    bind *:80
    mode http
    http-request set-var(txn.country) lua.get_country
    http-request deny if { var(txn.country) -m str CN }
    default_backend http_back

backend http_back
    mode http
    balance roundrobin
    server webserver localhost:8080 check
    timeout connect 30s
    timeout server 30s
EOF

Trong cấu hình này, -m str -i -f /usr/local/bin/geoiplookup.sh CN sẽ chạy script geoiplookup.sh với IP nguồn của yêu cầu như là tham số, và so sánh kết quả với CN. Nếu kết quả khớp, yêu cầu sẽ bị từ chối.

Khởi động lại HAProxy:

sudo service haproxy restart

Và đây là kết quả nếu sử dụng IP có mã quốc gia là CN truy cập vào webserver.

4. Sử dụng LUA thông tin quốc gia từ API.

Để sử dụng Lua để lấy dữ liệu vùng từ https://ipinfo.io và sau đó truyền vào HAProxy, bạn cần thực hiện các bước sau:

Cài đặt Lua, các file header của Lua và LuaSocket, một thư viện mạng cho Lua:

sudo apt install luarocks
sudo apt-get install lua5.3 lua-socket liblua5.3-dev -y

Cài đặt Lua và các thư viện cần thiết. Bạn sẽ cần thư viện lua-socket và lua-dkjson. Trên Ubuntu, bạn có thể cài đặt chúng bằng cách chạy các lệnh sau:

shell> luarocks install luasocket
Installing https://luarocks.org/luasocket-3.1.0-1.src.rock

luasocket 3.1.0-1 depends on lua >= 5.1 (5.3-1 provided by VM)
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/buffer.c -o src/buffer.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/compat.c -o src/compat.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/auxiliar.c -o src/auxiliar.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/options.c -o src/options.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/timeout.c -o src/timeout.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/io.c -o src/io.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/usocket.c -o src/usocket.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/unix.c -o src/unix.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/unixdgram.c -o src/unixdgram.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/unixstream.c -o src/unixstream.o -DLUASOCKET_DEBUG
gcc -shared -o socket/unix.so src/buffer.o src/compat.o src/auxiliar.o src/options.o src/timeout.o src/io.o src/usocket.o src/unix.o src/unixdgram.o src/unixstream.o
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/luasocket.c -o src/luasocket.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/timeout.c -o src/timeout.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/buffer.c -o src/buffer.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/io.c -o src/io.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/auxiliar.c -o src/auxiliar.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/options.c -o src/options.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/inet.c -o src/inet.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/except.c -o src/except.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/select.c -o src/select.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/tcp.c -o src/tcp.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/udp.c -o src/udp.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/compat.c -o src/compat.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/usocket.c -o src/usocket.o -DLUASOCKET_DEBUG
gcc -shared -o socket/core.so src/luasocket.o src/timeout.o src/buffer.o src/io.o src/auxiliar.o src/options.o src/inet.o src/except.o src/select.o src/tcp.o src/udp.o src/compat.o src/usocket.o
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/mime.c -o src/mime.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/compat.c -o src/compat.o -DLUASOCKET_DEBUG
gcc -shared -o mime/core.so src/mime.o src/compat.o
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/buffer.c -o src/buffer.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/compat.c -o src/compat.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/auxiliar.c -o src/auxiliar.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/options.c -o src/options.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/timeout.c -o src/timeout.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/io.c -o src/io.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/usocket.c -o src/usocket.o -DLUASOCKET_DEBUG
gcc -O2 -fPIC -I/usr/include/lua5.3 -c src/serial.c -o src/serial.o -DLUASOCKET_DEBUG
gcc -shared -o socket/serial.so src/buffer.o src/compat.o src/auxiliar.o src/options.o src/timeout.o src/io.o src/usocket.o src/serial.o
luasocket 3.1.0-1 is now installed in /usr/local (license: MIT)

root@haproxy1-13-206:/home# luarocks install dkjson
Installing https://luarocks.org/dkjson-2.6-1.src.rock

dkjson 2.6-1 depends on lua >= 5.1, < 5.5 (5.3-1 provided by VM)
dkjson 2.6-1 is now installed in /usr/local (license: MIT/X11)

Tiếp theo là cài đặt module lua-dkjson.

shell> luarocks install dkjson
Installing https://luarocks.org/dkjson-2.6-1.src.rock

dkjson 2.6-1 depends on lua >= 5.1, < 5.5 (5.3-1 provided by VM)
No existing manifest. Attempting to rebuild...
dkjson 2.6-1 is now installed in /usr/local (license: MIT/X11)

Tạo một script Lua để lấy dữ liệu vùng từ https://ipinfo.io. Script này sẽ gửi một yêu cầu HTTP GET đến https://ipinfo.io/[IP]/json và trả về thông tin vùng:

-- ipinfo.lua
local http = require("socket.http")
local ltn12 = require("ltn12")
local json = require("dkjson")

function get_country(ip)
    local response = {}
    local _, status_code, headers = http.request{
        url = "http://ipinfo.io/" .. ip .. "/json",
        sink = ltn12.sink.table(response),
    }
    if status_code ~= 200 then
        return nil, "HTTP request failed with status " .. status_code
    end
    local data, _, err = json.decode(table.concat(response))
    if err then
        return nil, "Error parsing JSON: " .. err
    end
    if not data.country then
        return nil, "No country found in response"
    end
    return data.country
end

-- Test the function with a specific IP
local ip = "8.8.8.8" -- Replace with the IP you want to test
local country, err = get_country(ip)
if country then
    print("Country for IP " .. ip .. " is: " .. country)
else
    print("Error: " .. err)
end

Kết quả khi chạy script trên.

shell> lua ipinfo.lua 
Country for IP 8.8.8.8 is: US

Nếu kết quả của bạn chạy tốt thì bạn cần xuất ra một biến từ hàm Lua để sử dụng trong HAProxy, bạn cần sử dụng đối tượng txn (transaction) được cung cấp bởi HAProxy. Đối tượng txn cho phép bạn đặt các biến có thể được truy cập từ cấu hình HAProxy.

Đầu tiên, bạn cần thay đổi chữ ký của hàm get_country để nhận đối tượng txn như một tham số. Sau đó, bạn có thể đặt một biến trên đối tượng txn để lưu trữ quốc gia:

cat > /home/ipinfo.lua << 'OEF'
-- ipinfo.lua
local http = require("socket.http")
local ltn12 = require("ltn12")
local json = require("dkjson")

function get_country(txn)
    local ip = txn.sf:src()
    local response = {}
    local _, status_code, headers = http.request{
        url = "http://ipinfo.io/" .. ip .. "/json",
        sink = ltn12.sink.table(response),
    }
    if status_code ~= 200 then
        txn:set_var("txn.country", "ERROR")
        return
    end
    local data, _, err = json.decode(table.concat(response))
    if err then
        txn:set_var("txn.country", "ERROR")
        return
    end
    if not data.country then
        txn:set_var("txn.country", "UNKNOWN")
        return
    end
    txn:set_var("txn.country", data.country)
end

core.register_action("get_country", { "http-req" }, get_country)
OEF

Bạn cũng có thể tạo một trang nội dung cảnh báo như sau:

cat > /home/403.html << 'OEF'
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
    <title>403 Forbidden</title>
</head>
<body>
    <h1>403 Forbidden</h1>
    <p>Request forbidden by administrative rules. You are accessing from country: "CN" (China) ()</p>
</body>
</html>
OEF

Sau đó, bạn có thể sử dụng biến txn.cn trong cấu hình HAProxy của bạn và dưới đây là cách bạn có thể thực hiện điều này:

cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
    lua-load /home/ipinfo.lua

defaults
    mode http
    errorfile 403 /home/403.html

frontend http_front
    bind *:80
    mode http
    # http-request set-var(txn.country) str(CN)
    http-request lua.get_country
    http-request deny if { var(txn.country) -m str CN }
    default_backend http_back

backend http_back
    mode http
    balance roundrobin
    server webserver localhost:8080 check
    timeout connect 30s
    timeout server 30s
EOF

Lưu ý rằng bạn cần cập nhật cách bạn gọi hàm get_country trong cấu hình HAProxy của bạn để truyền đối tượng txn như một tham số. Cách chính xác để làm điều này sẽ phụ thuộc vào cấu hình HAProxy cụ thể của bạn.

Bạn có thể chạy lệnh này để check lỗi, nếu không có kết quả trả về thì cấu hình HAProxy của bạn không có lỗi.

/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -c -q

Khởi động lại HAProxy:

sudo service haproxy restart

Như vậy cấu hình trên bao gồm đoạn code Lua để lấy địa chỉ IP của yêu cầu đến, gửi yêu cầu đến dịch vụ ipinfo.io để lấy thông tin quốc gia của IP đó, và sau đó đặt biến txn.country trong HAProxy với giá trị quốc gia nhận được.

Cấu hình HAProxy của bạn sau đó sẽ sử dụng hàm Lua get_country để lấy quốc gia của mỗi yêu cầu đến, và từ chối yêu cầu nếu quốc gia là ‘CN’ (Trung Quốc).

Bạn có thể sử dụng phương pháp giả lập một API sử dụng json-server theo bài https://wiki.hoanghd.com/gia-lap-mot-api-su-dung-json-server/ để giả lập kết quả API trên môi trường thử nghiệm nhé.

Và đây là kết quả nếu sử dụng IP có mã quốc gia là CN truy cập vào webserver.

Lưu ý rằng bạn cần cài đặt các thư viện Lua socket.httpltn12, và dkjson để đoạn mã Lua có thể hoạt động. Bạn cũng cần đảm bảo rằng đường dẫn đến file ipinfo.lua trong cấu hình HAProxy của bạn là chính xác.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

4,956FansLike
256FollowersFollow
223SubscribersSubscribe
spot_img

Related Stories