Thành thạo ssh-agent và SSH Key Forwarding trên Linux: Quản lý nhiều Key và Kết nối chuỗi Server An toàn

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Vấn đề: Quá nhiều Key, Quá nhiều Lần Nhập Mật khẩu

Nếu bạn đã từng quản lý hơn hai server Linux, bạn sẽ hiểu nỗi khổ này. Một SSH key cho GitHub, một key cho production, một key cho staging, rồi có thể thêm một key cho VPS của khách hàng. Mỗi lần kết nối lại phải nhập passphrase — hoặc tệ hơn, bạn dùng chung một key cho tất cả mọi nơi và cứ mong là không có gì xảy ra.

Mỗi kỹ sư xử lý vấn đề này theo cách khác nhau. Các cách tiếp cận khá đa dạng đến mức nếu chọn bừa, bạn sẽ phải trả giá sau này.

Bốn Cách Quản lý Nhiều SSH Key

Cách 1: Một Key cho Tất cả

Cách đơn giản nhất — một cặp key dùng cho tất cả server và dịch vụ. Hầu hết mọi người bắt đầu từ đây vì không cần cấu hình gì cả.

Cách 2: Key Riêng Từng Host với SSH Config

Tạo cặp key riêng cho từng server hoặc dịch vụ, rồi chỉ định key nào dùng cho host nào trong ~/.ssh/config:

Host github.com
    IdentityFile ~/.ssh/github_ed25519

Host production
    HostName 203.0.113.10
    User ubuntu
    IdentityFile ~/.ssh/prod_ed25519

Cách 3: ssh-agent với Key Loading

Khởi động một tiến trình ssh-agent, nạp các key vào đó một lần (chỉ nhập passphrase một lần duy nhất), và SSH tự động tái sử dụng chúng cho suốt phiên làm việc.

Cách 4: ssh-agent + SSH Key Forwarding

Cách cấu hình mạnh nhất. Agent của bạn chạy trên máy local. Khi bạn SSH vào bastion hoặc jump host, kết nối chuyển tiếp socket agent local sang máy đó — cho phép bạn xác thực đến tận các server nội bộ mà private key không bao giờ chạm vào ổ đĩa remote.

Ưu và Nhược Điểm

Cách 1: Một Key cho Tất cả

  • Ưu: Không cần cấu hình, dùng được ngay
  • Nhược: Một key bị lộ là mất quyền truy cập vào tất cả server
  • Nhược: Nếu bỏ passphrase cho tiện, private key trên ổ đĩa không được bảo vệ

Cách 2: Key Riêng Từng Host với SSH Config

  • Ưu: Cách ly tốt hơn — một key bị lộ, các key khác vẫn an toàn
  • Ưu: Config rõ ràng, dễ đọc, tự ghi chú key nào dùng ở đâu
  • Nhược: Vẫn phải nhập lại passphrase mỗi phiên, cho từng key
  • Nhược: Vô dụng khi cần nhảy từ server A sang server B mà không muốn sao chép private key lên A

Cách 3: Chỉ ssh-agent

  • Ưu: Nhập passphrase một lần mỗi phiên thay vì mỗi lần kết nối
  • Ưu: Private key vẫn được mã hóa trên ổ đĩa — agent chỉ giữ chúng trong bộ nhớ
  • Nhược: Agent mất khi đóng terminal trừ khi bạn cấu hình persistence
  • Nhược: Vẫn không thể kết nối chuỗi server mà không đặt key lên máy trung gian

Cách 4: ssh-agent + SSH Key Forwarding

  • Ưu: Private key không bao giờ rời khỏi máy local, kể cả kết nối nhiều chặng
  • Ưu: Một terminal, cả chuỗi: laptop → bastion → server nội bộ → database
  • Nhược: Bastion host phải đáng tin cậy — root bị chiếm trên đó có thể tạm thời lợi dụng socket được chuyển tiếp
  • Nhược: Cần cài đặt ban đầu nhiều hơn so với các cách khác

Cấu hình Được Khuyến nghị

Với hầu hết kỹ sư quản lý một vài server, Cách 4 xứng đáng để bỏ thời gian cài đặt. Key riêng từng host nạp vào ssh-agent, với forwarding chỉ bật trên các bastion host cụ thể mà bạn kiểm soát.

Giữ Cách 2 (SSH config với IdentityFile) làm nền tảng. Chồng ssh-agent lên trên để tiện nhập passphrase. Chỉ bật agent forwarding khi thực sự cần — không bao giờ bật toàn cục.

Trên server Ubuntu 22.04 production của tôi (4GB RAM), sự khác biệt với Ansible là rõ ngay lập tức. Các playbook kết nối đến 10+ node nội bộ qua một bastion duy nhất trước đây liên tục hỏi passphrase từng host — hoặc phải để key không mã hóa ngay trên bastion. Với agent forwarding, cả playbook chạy hoàn toàn tự động. Private key không bao giờ chạm vào filesystem remote.

Hướng dẫn Triển khai

Bước 1: Tạo Key Riêng Theo Mục đích

Dùng Ed25519 — nhanh hơn và mạnh hơn về mật mã so với RSA trong hầu hết trường hợp:

# Key cho GitHub
ssh-keygen -t ed25519 -C "github" -f ~/.ssh/github_ed25519

# Key cho server production
ssh-keygen -t ed25519 -C "prod-servers" -f ~/.ssh/prod_ed25519

# Key cho VPS của khách hàng
ssh-keygen -t ed25519 -C "client-vps" -f ~/.ssh/client_ed25519

Luôn đặt passphrase mạnh khi được hỏi. Không có passphrase, file key trên ổ đĩa chỉ là một file text chờ bị đánh cắp.

Bước 2: Khởi động ssh-agent

Desktop GNOME và KDE thường tự khởi động SSH agent khi bắt đầu phiên làm việc. Kiểm tra trước:

echo $SSH_AUTH_SOCK
# Nếu có agent, sẽ in ra đường dẫn như: /run/user/1000/keyring/ssh

Nếu biến rỗng, khởi động thủ công:

eval "$(ssh-agent -s)"
# Agent pid 12345

Để tự khởi động trong mỗi phiên shell, thêm đoạn này vào ~/.bashrc hoặc ~/.zshrc:

if [ -z "$SSH_AUTH_SOCK" ]; then
    eval "$(ssh-agent -s)"
fi

Bước 3: Thêm Key vào Agent

ssh-add ~/.ssh/github_ed25519
# Nhập passphrase: (nhập một lần — xong cho cả phiên)

ssh-add ~/.ssh/prod_ed25519
ssh-add ~/.ssh/client_ed25519

# Xác nhận các key đã nạp
ssh-add -l
# 256 SHA256:abc123... github (ED25519)
# 256 SHA256:def456... prod-servers (ED25519)

Trên máy dùng chung hoặc ít tin cậy, hãy đặt giới hạn thời gian. Key tự xóa khi hết hạn:

# Tự xóa sau 4 giờ (14400 giây)
ssh-add -t 14400 ~/.ssh/prod_ed25519

Bước 4: Cấu hình ~/.ssh/config

Gán key cho từng host và chỉ bật forwarding trên các bastion host đáng tin cậy:

Host github.com
    IdentityFile ~/.ssh/github_ed25519
    AddKeysToAgent yes

Host bastion
    HostName 203.0.113.5
    User ubuntu
    IdentityFile ~/.ssh/prod_ed25519
    ForwardAgent yes        # Chỉ bật cho host đáng tin cậy này
    AddKeysToAgent yes

Host internal-db
    HostName 10.0.1.20
    User ubuntu
    ProxyJump bastion       # Kết nối qua bastion — không cần IdentityFile ở đây

Host client-vps
    HostName 198.51.100.10
    User root
    IdentityFile ~/.ssh/client_ed25519
    AddKeysToAgent yes
    # ForwardAgent cố ý bỏ qua — không forward đến host bên ngoài

Bước 5: Kiểm tra Chuỗi Kết nối

Với cấu hình này, kết nối đến database nội bộ từ laptop của bạn chỉ cần một lệnh duy nhất:

ssh internal-db

SSH thực sự làm những bước sau:

  1. Kết nối đến bastion bằng prod key local của bạn qua agent được chuyển tiếp
  2. Mở kết nối thứ hai từ bastion đến 10.0.1.20
  3. Server nội bộ xác thực với agent local được chuyển tiếp của bạn
  4. File private key không bao giờ chạm vào ổ đĩa của bastion

Để xác nhận forwarding đang hoạt động ở đầu remote:

# Sau khi kết nối vào bastion:
echo $SSH_AUTH_SOCK
# /tmp/ssh-XXXXXX/agent.NNNN  ← socket được chuyển tiếp đã có mặt

ssh-add -l
# Liệt kê các key từ máy local của bạn

Bước 6: Duy trì Agent Qua Các Phiên Terminal

Một điều bất tiện còn lại: đóng terminal và agent mất, kéo theo tất cả key đã nạp. Tiện ích keychain giải quyết vấn đề này bằng cách giữ một agent duy nhất sống qua các phiên làm việc:

sudo apt install keychain

# Thêm vào ~/.bashrc:
eval "$(keychain --eval --quiet ~/.ssh/prod_ed25519 ~/.ssh/github_ed25519)"

Lần đăng nhập đầu tiên sau khi khởi động lại máy, bạn nhập passphrase một lần. Mọi terminal mở sau đó — tab mới, kết nối lại, pane tmux — đều tái sử dụng agent đang chạy. Không còn phải nhập passphrase nữa.

Checklist Bảo mật Trước khi Triển khai Thực tế

  • Chỉ đặt ForwardAgent yes trên các bastion host bạn hoàn toàn kiểm soát
  • Không bao giờ thêm ForwardAgent yes vào block Host * (áp dụng cho tất cả)
  • Dùng ssh-add -t <giây> cho key nạp trên máy dùng chung
  • Kiểm tra những gì đang được nạp bằng ssh-add -l trước các thao tác nhạy cảm
  • Giới hạn quyền truy cập /tmp trên bastion và tắt SSH login cho root

Rủi ro thực sự của agent forwarding không phải là key bị sao chép — byte private key của bạn thực sự vẫn ở máy local. Điểm dễ bị khai thác là bản thân socket đó. Một root user trên bastion có thể tạm thời dùng socket đó trong khi phiên SSH của bạn đang mở. Hãy đối xử với bastion host nghiêm túc như với chính laptop của bạn.

Cài đặt một lần và việc xác thực không còn là thứ bạn phải bận tâm nữa. Gõ ssh internal-db là vào được ngay. Key của bạn ở đúng chỗ của nó — trên máy local.

Share: