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:8080
và localhost: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.http
, ltn12
, 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.