Triển khai Apache Guacamole làm Bastion Host trên Docker: Truy cập SSH và RDP an toàn qua Web với 2FA

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Báo động lúc nửa đêm: Vì sao mô hình Bastion truyền thống khiến chúng tôi kiệt sức

Vào lúc 2:14 sáng một ngày thứ Ba, chuông báo PagerDuty réo liên hồi. Một jump box công khai ra internet đang trên bờ vực sụp đổ dưới đợt tấn công vét cạn (brute-force) với hơn 45.000 lượt xác thực SSH thất bại mỗi phút. Tệp /var/log/auth.log phình to vượt mức 38 GB chỉ sau chưa đầy bốn giờ, làm đầy phân vùng root và đánh sập ba tiến trình chạy nền quan trọng. Sự cố đêm hôm đó buộc chúng tôi phải định hình lại toàn bộ phương thức truy cập từ xa cho 34 máy chủ đám mây của mình.

Các bastion host truyền thống tạo ra gánh nặng vận hành liên tục. Kỹ sư phải quản lý hàng tá private key rải rác trên laptop cá nhân, vật lộn với các tệp cấu hình OpenSSH phức tạp cùng chuỗi chỉ thị ProxyJump dài ngoằng.

Trong khi đó, VPN client thường xuyên rớt kết nối mỗi khi chuyển mạng Wi-Fi. Ở môi trường Windows, việc mở cổng RDP 3389 ra internet chẳng khác nào mở cửa mời gọi các công cụ quét mã độc tống tiền (ransomware) tự động ghé thăm chỉ sau vài phút. Chúng tôi cần một cổng truy cập tập trung duy nhất: ẩn toàn bộ các cổng quản trị nội bộ, bắt buộc xác thực đa yếu tố (MFA), và hoạt động mượt mà trên mọi trình duyệt hiện đại mà không yêu cầu cài thêm bất kỳ agent hay plugin cục bộ nào.

Cơ chế hoạt động của Apache Guacamole khi truy cập không cần Client

Apache Guacamole hoạt động như một cổng kết nối máy tính từ xa không cần cài đặt client (clientless remote desktop gateway). Thay vì chạy các ứng dụng RDP hoặc SSH gốc trên máy của kỹ sư, toàn bộ phiên làm việc sẽ diễn ra bên trong một ứng dụng web HTML5. Phía máy chủ sẽ biên dịch các giao thức điều khiển từ xa thô thành luồng dữ liệu tối ưu hóa gồm các cập nhật canvas và sự kiện DOM, truyền tải qua cổng HTTPS (cổng 443) chuẩn cùng WebSockets.

Hệ thống được vận hành bởi ba thành phần cốt lõi:

  • guacd (Guacamole Proxy Daemon): Một daemon native viết bằng C cực kỳ nhẹ, chịu trách nhiệm mở các kết nối SSH, RDP hoặc VNC ngược dòng (upstream) tới các máy chủ nội bộ. Nó chuyển đổi dữ liệu đồ họa và bộ đệm terminal gốc thành các tập lệnh của giao thức Guacamole.
  • guacamole-client: Một Java servlet chạy trên Apache Tomcat. Thành phần này kết xuất giao diện quản trị, xác thực thông tin đăng nhập của người dùng và chuyển tiếp dữ liệu luồng tới guacd.
  • PostgreSQL + Plugin TOTP: Lưu trữ các tham số kết nối đã mã hóa, phân quyền người dùng và nhật ký kiểm toán (audit logs), đồng thời yêu cầu mã xác thực 6 chữ số dùng một lần dựa trên thời gian (TOTP) tiêu chuẩn khi đăng nhập.

Một reverse proxy Nginx được đặt ở phía trước để xử lý việc kết thúc mã hóa TLS (TLS termination). Lưu lượng truy cập từ bên ngoài chỉ chạm tới endpoint HTTPS duy nhất được bảo vệ bởi 2FA. Trong khi đó, các cổng upstream như SSH (cổng 22) và RDP (cổng 3389) hoàn toàn được cô lập an toàn bên trong mạng Docker bridge hoặc subnet riêng tư trên AWS/GCP.

Hướng dẫn triển khai: Docker, PostgreSQL và TOTP

Việc biên dịch Guacamole từ mã nguồn trực tiếp trên máy chủ vật lý đòi hỏi phải cài đặt hàng loạt thư viện C, build các gói phụ thuộc đồ họa Cairo và cấu hình các tệp XML phức tạp của Tomcat. Docker Compose giúp quá trình này trở nên gọn gàng, chuẩn hóa và dễ dàng tái lập. Dưới đây là cấu hình hoàn chỉnh đang vận hành trên môi trường production của chúng tôi.

1. Thiết lập thư mục và khởi tạo cơ sở dữ liệu

Tạo cấu trúc thư mục cần thiết và trích xuất cấu trúc cơ sở dữ liệu PostgreSQL trực tiếp từ image chính thức:

mkdir -p /opt/guacamole/{init,data}
cd /opt/guacamole

# Tạo tệp schema PostgreSQL chính thức
docker run --rm guacamole/guacamole:1.5.5 \
  /opt/guacamole/bin/initdb.sh --postgresql > ./init/01-initdb.sql

2. Xây dựng tệp docker-compose.yml

Tạo tệp docker-compose.yml trong thư mục /opt/guacamole để kết nối proxy daemon, cơ sở dữ liệu, giao diện web và reverse proxy:

services:
  guacd:
    image: guacamole/guacd:1.5.5
    container_name: guacamole-daemon
    restart: always
    networks:
      - guac-network

  postgres:
    image: postgres:15-alpine
    container_name: guacamole-db
    restart: always
    environment:
      POSTGRES_DB: guacamole_db
      POSTGRES_USER: guacamole_user
      POSTGRES_PASSWORD: StrongProductionPasswordGoesHere!
    volumes:
      - ./init:/docker-entrypoint-initdb.d:ro
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - guac-network

  guacamole:
    image: guacamole/guacamole:1.5.5
    container_name: guacamole-web
    restart: always
    depends_on:
      - guacd
      - postgres
    environment:
      GUACD_HOSTNAME: guacd
      GUACD_PORT: 4822
      POSTGRESQL_HOSTNAME: postgres
      POSTGRESQL_DATABASE: guacamole_db
      POSTGRESQL_USER: guacamole_user
      POSTGRESQL_PASSWORD: StrongProductionPasswordGoesHere!
      # Cấu hình xác thực hai yếu tố TOTP
      TOTP_ENABLED: "true"
      TOTP_ISSUER: "ITFromZero-Gateway"
      TOTP_DIGITS: 6
      TOTP_PERIOD: 30
      TOTP_MODE: "sha1"
    networks:
      - guac-network

  nginx:
    image: nginx:alpine
    container_name: guacamole-nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - guacamole
    networks:
      - guac-network

networks:
  guac-network:
    driver: bridge

3. Cấu hình Nginx hỗ trợ WebSockets

Guacamole yêu cầu WebSockets để đảm bảo thao tác phím có độ trễ cực thấp và con trỏ chuột di chuyển mượt mà. Lưu cấu hình sau vào tệp nginx.conf:

server {
    listen 80;
    server_name bastion.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name bastion.example.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://guacamole:8080/guacamole/;
        proxy_buffering off;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        access_log off;
    }
}

4. Khởi chạy hệ thống và thiết lập 2FA

Khởi chạy các container ở chế độ chạy nền:

docker compose up -d
docker compose logs -f guacamole

Truy cập https://bastion.example.com trên trình duyệt của bạn. Tài khoản quản trị mặc định là guacadmin / guacadmin.

  1. Quét mã QR đăng ký bằng ứng dụng xác thực như Bitwarden, 1Password hoặc Aegis.
  2. Truy cập ngay vào Settings > Users > guacadmin, đổi mật khẩu mặc định và tạo tài khoản định danh riêng cho từng kỹ sư.
  3. Vô hiệu hóa tài khoản guacadmin sau khi đã kiểm tra và đảm bảo tài khoản quản trị cá nhân của bạn cùng mã TOTP hoạt động ổn định.

5. Thêm máy chủ đích SSH và RDP

Việc cấu hình các máy chủ nội bộ chỉ mất khoảng 30 giây cho mỗi máy:

  • Máy chủ Linux (SSH): Vào Settings > Connections > New Connection. Chọn giao thức SSH. Điền IP nội bộ (ví dụ: 10.0.1.15), cổng 22 và username. Dán trực tiếp private key dùng để deploy vào giao diện web. Kỹ sư có thể truy cập máy chủ ngay lập tức mà không cần phải lưu trữ bản sao của private key trên thiết bị cá nhân.
  • Máy chủ Windows (RDP): Tạo kết nối mới với giao thức RDP. Nhập IP nội bộ và cổng 3389. Tích chọn Ignore cert nếu sử dụng chứng chỉ tự ký (self-signed), và đặt chế độ bảo mật thành NLA (Network Level Authentication).

Kinh nghiệm vận hành thực tế

Chuyển đổi toàn bộ quyền truy cập từ xa về sau Guacamole đã rút ngắn thời gian chuẩn bị môi trường cho nhân sự mới từ hai ngày cấu hình máy xuống còn đúng năm phút. Lập trình viên mới chỉ cần đăng nhập vào cổng web, hoàn tất 2FA là có thể truy cập ngay terminal hoặc desktop. Không còn ai phải đau đầu với cấu hình OpenSSH cục bộ hay ứng dụng VPN của công ty nữa.

Khả năng ghi nhật ký kiểm toán (audit logging) cũng là một điểm cộng rất lớn. Guacamole tích hợp sẵn tính năng ghi lại phiên làm việc, tự động lưu các tệp phát lại phiên được nén trực tiếp vào ổ đĩa. Bạn có thể xem lại toàn bộ thao tác màn hình và từng phím bấm trong các đợt can thiệp production mà không cần cài đặt các agent giám sát nặng nề lên máy chủ đích.

Hãy lưu ý mức tiêu thụ RAM khi tải cao. Mỗi kết nối SSH đang hoạt động chỉ tốn khoảng 15 MB RAM trong guacd. Ngược lại, mỗi phiên RDP độ phân giải 1080p đầy đủ tiêu tốn khoảng 120 MB đến 200 MB tùy thuộc vào độ sâu màu và tần suất làm mới màn hình. Cấu hình VM với 4 GB RAM hoàn toàn đáp ứng tốt 18 phiên làm việc đồng thời của chúng tôi với độ trễ gõ phím dưới 30ms. Nếu hạ tầng của bạn vẫn đang để lộ các cổng quản trị trực tiếp ra internet, việc dựng cổng gateway đóng gói bằng container này sẽ giúp bạn thu hẹp bề mặt tấn công chỉ trong một buổi chiều.

Share: