Tóm tắt: Khai báo mọi host trong ~/.ssh/config: bastion dùng ProxyJump, bật ControlMaster auto + ControlPersist để các lần SSH sau dùng lại socket (nhanh hơn nhiều với Ansible/scp). Không bật ForwardAgent bừa bãi; qua bastion thì ProxyJump đã đủ. Cần OpenSSH ≥ 7.3 cho ProxyJump.
Luồng hoạt động
flowchart LR L["Laptop (key trong ssh-agent)"] -->|"SSH 1: tới bastion"| B["bastion.example.com"] B -->|"TCP forward, không giải mã"| R1["pe1 198.51.100.11"] B -->|"TCP forward"| R2["pe2 198.51.100.12"] L -.->|"SSH 2: end-to-end qua đường hầm"| R1 L --> S["ControlPath socket"] S -.->|"lần sau dùng lại"| L
Với ProxyJump, phiên SSH tới router được mã hoá end-to-end từ laptop; bastion chỉ chuyển tiếp TCP, private key không rời laptop.
Cấu hình
~/.ssh/config
# Rule đầu tiên khớp sẽ thắng cho từng option: đặt Host cụ thể lên trên, "Host *" ở cuối
Host bastion
HostName bastion.example.com
User ops
Port 22
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
# Router/thiết bị sau bastion
Host pe1
HostName 198.51.100.11
Host pe2
HostName 198.51.100.12
Host pe* 198.51.100.*
User automation
ProxyJump bastion
# Thiết bị cũ chỉ hỗ trợ thuật toán yếu: chỉ mở cho đúng nhóm này
Host legacy-*
KexAlgorithms +diffie-hellman-group14-sha1
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
ControlMaster auto
ControlPath ~/.ssh/cm-%C
ControlPersist 10m
AddKeysToAgent yes
HashKnownHosts yes| Option | Ý nghĩa |
|---|---|
ProxyJump bastion |
Tương đương ssh -J bastion. Nhiều tầng: ProxyJump jump1,jump2 |
ControlMaster auto |
Có socket sẵn thì dùng lại, chưa có thì tạo master mới |
ControlPath ~/.ssh/cm-%C |
%C là hash của host/port/user, đường dẫn ngắn, tránh lỗi socket path quá dài |
ControlPersist 10m |
Master chạy nền thêm 10 phút sau khi phiên cuối đóng |
IdentitiesOnly yes |
Chỉ thử key chỉ định, tránh Too many authentication failures khi agent có nhiều key |
ServerAliveInterval 30 |
Gửi keepalive mỗi 30s, giữ phiên qua NAT/firewall |
AddKeysToAgent yes |
Tự nạp key vào agent sau lần nhập passphrase đầu |
chmod 600 ~/.ssh/config và chmod 700 ~/.ssh, nếu không ssh có thể từ chối đọc file.
ssh-agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l # key đang có trong agent
ssh-add -t 4h ~/.ssh/id_ed25519 # key tự hết hạn sau 4 giờForwardAgent yes cho phép root trên máy đích dùng agent của bạn để SSH đi nơi khác trong lúc bạn đang kết nối. Chỉ bật cho host thật sự tin cậy và cần (vd git pull trên server). Đi qua bastion thì dùng ProxyJump, không cần agent forwarding.
Port forward
| Mục đích | Lệnh |
|---|---|
| Qua bastion một lần, không cần config | ssh -J ops@bastion.example.com automation@198.51.100.11 |
Local forward: mở Grafana nội bộ ở localhost:3000 |
ssh -N -L 3000:198.51.100.20:3000 bastion |
| Local forward gNMI router về máy mình | ssh -N -L 57400:198.51.100.11:57400 bastion |
| Remote forward: cho server gọi ngược về port 8080 local | ssh -N -R 8080:localhost:8080 server |
| SOCKS proxy động | ssh -N -D 1080 bastion rồi dùng socks5://127.0.0.1:1080 |
| Chạy nền sau khi xác thực | thêm -f: ssh -f -N -L 3000:198.51.100.20:3000 bastion |
| scp/rsync qua bastion | scp -J bastion file pe1:/tmp/ / rsync -e 'ssh -J bastion' -a dir/ pe1:/tmp/dir/ |
Đặt sẵn trong config:
Host grafana-tunnel
HostName bastion.example.com
User ops
LocalForward 3000 198.51.100.20:3000
ExitOnForwardFailure yesQuản lý ControlMaster
ssh -O check pe1 # master còn sống không
ssh -O exit pe1 # đóng master (khi đổi config, đổi key)
ssh -G pe1 | grep -Ei '^(hostname|user|proxyjump|controlpath) ' # config cuối cùng áp cho host
ssh -vvv pe1 # debug thuật toán, key được thửLỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
Too many authentication failures |
Agent đưa nhiều key, server cắt trước key đúng | IdentitiesOnly yes + IdentityFile |
no matching key exchange method found / no matching host key type |
Thiết bị cũ chỉ có SHA1/ssh-rsa | Thêm KexAlgorithms +... / HostKeyAlgorithms +ssh-rsa chỉ cho host đó |
| Sửa config mà vẫn hành xử như cũ | Đang dùng lại ControlMaster cũ | ssh -O exit <host> |
ControlPath too long |
Đường dẫn kiểu %r@%h:%p dài |
Dùng %C |
Option trong Host * không ăn |
Option đã được gán bởi block khớp trước | Đặt Host * cuối file |
bind: Address already in use khi -L |
Port local đang bị dùng | Đổi port hoặc ss -tlpn 'sport = :3000' |
REMOTE HOST IDENTIFICATION HAS CHANGED |
Router thay RP/cài lại, host key đổi | Xác minh với người vận hành rồi ssh-keygen -R 198.51.100.11 |
| Phiên treo sau vài phút idle | NAT/firewall timeout | ServerAliveInterval 30 |
Tham khảo
- ssh_config(5), ssh(1)
- SSH hardening cho phía server.
- Ansible network troubleshooting khi
network_cliđi qua bastion.