SSH Tunneling: Truy Cập An Toàn Dịch Vụ Từ Xa Trên Bất Kỳ Mạng Nào

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

Ba Cách Tạo Tunnel Với SSH

Khi bạn cần truy cập một dịch vụ ẩn sau firewall — một cơ sở dữ liệu, ứng dụng web nội bộ, Redis cache — bạn có một vài lựa chọn: mở cổng firewall, thiết lập VPN, hoặc dùng SSH tunneling. Mỗi cách đều có chỗ đứng của nó. Hãy để tôi so sánh chúng trước khi đi vào chi tiết SSH.

Lựa chọn 1: Mở Cổng Firewall

Cách tiếp cận thô bạo nhất: đục một lỗ để traffic chạy qua. Nó hoạt động được, nhưng lại phơi bày dịch vụ ra internet công cộng. Dù có bảo vệ bằng mật khẩu, một cổng database có thể truy cập công khai vẫn là lời mời chào cho các cuộc tấn công brute-force và vulnerability scanner chạy 24/7.

Lựa chọn 2: Thiết Lập VPN

VPN đặt máy của bạn ảo vào trong mạng từ xa thông qua tunnel được mã hóa. Tuyệt vời cho team và môi trường doanh nghiệp — nhưng nó đòi hỏi VPN server, phần mềm client, quản lý certificate, và bảo trì liên tục. Với một developer đơn độc chỉ cần truy cập staging database, đó là sự phức tạp thừa thãi.

Lựa chọn 3: SSH Tunneling

SSH tunneling chuyển tiếp cổng từ máy này sang máy khác thông qua kết nối SSH sẵn có. Không cần phần mềm thêm, không cần certificate authority, không cần quy tắc firewall mới. Nếu bạn có thể SSH vào server, bạn có thể tunnel qua nó.

Có ba loại tunnel, mỗi loại giải quyết vấn đề khác nhau:

  • Local forwarding — truy cập dịch vụ từ xa như thể nó đang chạy trên máy local của bạn
  • Remote forwarding — phơi bày cổng local ra server từ xa
  • Dynamic forwarding (SOCKS proxy) — định tuyến traffic tùy ý qua SSH server

Ưu và Nhược Điểm Của Mỗi Cách

Local Port Forwarding

Đây là loại tunnel bạn sẽ dùng thường xuyên nhất. Giả sử staging server của bạn chạy PostgreSQL trên cổng 5432, nhưng cổng đó không được phơi bày công khai. Chuyển tiếp nó về máy local của bạn như sau:

ssh -L 5433:localhost:5432 [email protected]

Trỏ database client của bạn vào localhost:5433 và bạn đang kết nối tới instance PostgreSQL từ xa như thể nó đang chạy local. Các công cụ như DBeaver, DataGrip, hay psql thông thường đều hoạt động mà không cần cấu hình đặc biệt nào.

  • Ưu: Cực kỳ đơn giản — không cần file config cho truy cập một lần
  • Ưu: Hoạt động qua hầu hết firewall cho phép SSH (cổng 22)
  • Nhược: Tunnel sẽ chết khi phiên SSH của bạn kết thúc
  • Nhược: Chỉ chuyển tiếp một cổng mỗi lệnh — quản lý bốn dịch vụ nghĩa là bốn tab terminal

Remote Port Forwarding

Remote forwarding đảo ngược chiều hướng. Bạn phơi bày thứ gì đó trên máy local của mình ra server từ xa. Các use case điển hình: test webhook receiver cần public URL, hoặc truy cập máy dev đặt sau NAT không thể nhận kết nối đến.

ssh -R 8080:localhost:3000 [email protected]

Sau lệnh này, traffic đến public-server.com:8080 sẽ chuyển đến cổng local 3000 của bạn.

  • Ưu: Làm cho dịch vụ local có thể truy cập từ internet mà không cần đụng vào port forwarding của router
  • Nhược: Yêu cầu GatewayPorts yes trong sshd_config của server để nhận kết nối từ bên ngoài localhost
  • Nhược: Rủi ro bảo mật nếu không khóa chặt — bất kỳ ai trên server từ xa đều có thể truy cập máy của bạn qua tunnel

Dynamic Forwarding (SOCKS Proxy)

Dynamic forwarding biến kết nối SSH thành SOCKS5 proxy. Traffic bạn định tuyến qua đó sẽ thoát ra từ mạng của SSH server — tiện lợi khi truy cập tài nguyên nội bộ công ty hoặc kiểm tra hành vi bị hạn chế theo địa lý.

ssh -D 1080 [email protected]
  • Ưu: Một lệnh proxy tất cả traffic hỗ trợ SOCKS — trình duyệt, curl, git, bất cứ thứ gì hỗ trợ cài đặt proxy
  • Ưu: Hữu ích để duyệt tài nguyên nội bộ mà không cần VPN đầy đủ
  • Nhược: Không phải ứng dụng nào cũng hỗ trợ SOCKS5 gốc — bạn có thể cần wrapper như proxychains
  • Nhược: Yêu cầu cấu hình proxy settings của từng ứng dụng riêng biệt

Thiết Lập Được Khuyến Nghị

Cho công việc phát triển hàng ngày, kết hợp hai thứ: các mục SSH config cho những tunnel thường dùng của bạn, và flag -N để chạy tunnel ở nền mà không mở interactive shell.

Thêm Định Nghĩa Tunnel Vào ~/.ssh/config

Thôi đừng gõ đi gõ lại các lệnh dài. Định nghĩa tunnel một lần trong ~/.ssh/config:

Host staging-db-tunnel
    HostName staging-server.com
    User deploy
    LocalForward 5433 localhost:5432
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host prod-redis-tunnel
    HostName prod-server.com
    User deploy
    LocalForward 6380 localhost:6379
    ServerAliveInterval 60
    ServerAliveCountMax 3

Sau đó khởi động tunnel bất kỳ với một lệnh ngắn:

ssh -N staging-db-tunnel &
ssh -N prod-redis-tunnel &

Flag -N báo SSH không thực thi lệnh từ xa — nó chỉ giữ kết nối mở và duy trì port forwarding. Ký tự & đưa tiến trình vào nền.

ServerAliveIntervalServerAliveCountMax quan trọng hơn nhiều người nghĩ. Không có chúng, tunnel nhàn rỗi sẽ bị ngắt âm thầm sau vài phút — và bạn sẽ không biết cho đến khi query database bị timeout. Với các thiết lập này, SSH gửi keepalive mỗi 60 giây và chịu đựng 3 lần không phản hồi trước khi đóng. Đó là khoảng 3 phút chịu đựng các sự cố mạng tạm thời, đủ để xử lý hầu hết gián đoạn ngắn trên hạ tầng cloud.

Dùng autossh Cho Tunnel Chạy Lâu Dài

Cần một tunnel duy trì hoạt động vô thời hạn? autossh giám sát kết nối và tự động khởi động lại khi nó bị ngắt:

# Cài đặt autossh
sudo apt install autossh   # Debian/Ubuntu
brew install autossh       # macOS

# Khởi động tunnel liên tục
autossh -M 0 -N -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" \
  -L 5433:localhost:5432 [email protected]

Flag -M 0 vô hiệu hóa cổng giám sát riêng của autossh và thay vào đó dựa vào keepalive tích hợp của SSH — cách tiếp cận được khuyến nghị hiện tại, vì cách dùng monitoring port rất mong manh và thêm độ phức tạp không cần thiết.

Hướng Dẫn Triển Khai

Bước 1: Tunnel Local Cơ Bản

Bắt đầu đơn giản. Xác minh kết nối SSH hoạt động, sau đó kiểm tra tunnel kết nối được trước khi xây dựng tiếp:

# Chuyển tiếp cổng remote 5432 về cổng local 5433
ssh -L 5433:localhost:5432 -N [email protected]

# Trong terminal khác, kiểm tra kết nối
psql -h localhost -p 5433 -U dbuser mydb

Bước 2: Multi-Hop Tunneling

Dịch vụ đích không nằm trên chính SSH server? Không sao. Nếu jump server có thể truy cập được nó, bạn cũng có thể — chỉ cần chỉ định hostname nội bộ trong quy tắc forwarding:

# Định dạng: -L cổng_local:host_đích:cổng_đích
ssh -L 5433:db-internal.private:5432 -N [email protected]

db-internal.private được phân giải từ góc nhìn của jump-server.com, không phải máy local của bạn. Điều này cho phép bạn truy cập các host thậm chí không có DNS entry công khai.

Bước 3: Jump Host Với ProxyJump

OpenSSH 7.3 (phát hành 2016) đã giới thiệu ProxyJump — phương thức thay thế gọn gàng hơn cho mẫu ProxyCommand netcat cũ mà nhiều hướng dẫn vẫn còn dùng. Định nghĩa chuỗi một lần trong SSH config của bạn:

Host internal-app
    HostName 10.0.1.50
    User appuser
    ProxyJump [email protected]
    LocalForward 8080 localhost:8080

Sau đó chỉ cần chạy:

ssh -N internal-app

SSH đàm phán kết nối hai chặng tự động. Không cần netcat thủ công, không cần thủ thuật shell.

Bước 4: Systemd Service Cho Tunnel Liên Tục

Thiết lập này đã chạy trên production mà không cần giám sát trong hơn một năm. Khởi động lại, mạng bị ngắt, SSH disconnect — autossh xử lý tất cả. Chìa khóa là đóng gói mọi thứ trong systemd service:

# /etc/systemd/system/ssh-tunnel-db.service
[Unit]
Description=SSH Tunnel tới Staging DB
After=network.target

[Service]
User=deploy
ExecStart=/usr/bin/autossh -M 0 -N \
  -o "ServerAliveInterval=60" \
  -o "ServerAliveCountMax=3" \
  -o "ExitOnForwardFailure=yes" \
  -L 5433:localhost:5432 \
  [email protected]
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable ssh-tunnel-db
sudo systemctl start ssh-tunnel-db
sudo systemctl status ssh-tunnel-db

Đừng bỏ qua ExitOnForwardFailure=yes. Không có nó, autossh sẽ vui vẻ tiếp tục chạy trong khi tunnel âm thầm thất bại trong việc bind — chẳng hạn vì một tiến trình khác đã chiếm cổng local đó. Bạn sẽ thấy trạng thái service màu xanh nhưng không có kết nối thực sự. Tùy chọn này buộc autossh thất bại ồn ào để systemd có thể khởi động lại nó sạch sẽ.

Danh Sách Kiểm Tra Bảo Mật Nhanh

  • Dùng xác thực SSH key — vô hiệu hóa xác thực bằng mật khẩu trên server (PasswordAuthentication no trong sshd_config)
  • Kiểm soát quyền port forwarding với AllowTcpForwarding — dùng khối Match User trong sshd_config để hạn chế theo từng user
  • Với remote forwarding, bind vào localhost trừ khi truy cập bên ngoài là có chủ ý: -R 127.0.0.1:8080:localhost:3000
  • Kiểm tra các cổng đang được chuyển tiếp trên server: ss -tlnp | grep ssh

SSH tunneling không thể thay thế VPN cho team 50 người. Nhưng với một developer đơn độc hay team nhỏ quản lý một vài dịch vụ, nó thường là tất cả những gì bạn cần. Thiết lập các mục config một lần, và việc truy cập database từ xa hay dịch vụ nội bộ chỉ còn là một lệnh duy nhất — kênh mã hóa đi kèm, không cần hạ tầng thêm.

Share: