Giao diện
SSH & Networking Security — Bảo Mật Kết Nối 🔐
Thứ Sáu, 11 giờ đêm. Hệ thống monitoring báo CPU production server đang 100%. Bạn SSH vào — hàng chục process lạ đang chạy. last -20 hiện login từ IP ở Nga. Nguyên nhân? Password auth vẫn bật, password là admin123, và không có firewall. Server đã trở thành crypto miner cho hacker.
Bài học này: SSH key auth, SSH config cho productivity, tunneling cho security, firewall cho defense — tất cả những gì bạn cần để kết nối an toàn và bảo vệ server.
1. SSH Key-Based Auth — Passwords Là Không Đủ 🔑
🎯 Mục tiêu
Sau bài học này, bạn sẽ:
- Hiểu tại sao SSH key vượt trội so với password authentication
- Thiết lập SSH key pair (Ed25519), copy lên server, và disable password auth
- Viết SSH config file để kết nối nhanh hơn 10 lần
- Sử dụng SSH tunneling (local, remote, dynamic) cho các tình huống thực tế
- Cấu hình firewall cơ bản với
ufwtheo nguyên tắc least privilege - Phân tích và xử lý incident khi server bị compromise qua SSH
Tại sao SSH keys > Passwords?
| Tiêu chí | Password Auth | SSH Key Auth |
|---|---|---|
| Brute-force resistance | P@ssw0rd123 → crack trong vài giờ | Ed25519/RSA-4096 → không thể brute-force |
| Intercept risk | Password truyền qua network (dù encrypted) | Private key không bao giờ rời máy bạn |
| Per-host control | Cùng password cho mọi server? 🤦 | Mỗi server một key riêng, restrict per-command |
| Automation | Script nhập password = nightmare | CI/CD, deploy script dùng key tự nhiên |
| Revocation | Đổi password → thông báo cả team | Xóa public key khỏi authorized_keys → xong |
Setup workflow — từ zero đến key-based auth
bash
# 1. Generate key pair (Ed25519 recommended — nhanh, an toàn, compact)
ssh-keygen -t ed25519 -C "deploy@mycompany.com"
# → ~/.ssh/id_ed25519 (private — KHÔNG BAO GIỜ chia sẻ)
# → ~/.ssh/id_ed25519.pub (public — copy lên server)
# 2. Set correct permissions (CRITICAL!)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 # Private key: chỉ owner đọc
chmod 644 ~/.ssh/id_ed25519.pub # Public key: ai đọc cũng được
# 3. Copy public key lên server
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@production.example.com
# ← Tự động thêm vào ~/.ssh/authorized_keys trên server
# 4. TEST key auth trước khi disable password!
ssh deploy@production.example.com
# Nếu login thành công mà KHÔNG hỏi password → key hoạt động ✅
# 5. Disable password auth trên server (SAU KHI xác nhận key works!)
# Edit /etc/ssh/sshd_config:
# PasswordAuthentication no
# PermitRootLogin no
# PubkeyAuthentication yes
sudo systemctl restart sshd🚨 CRITICAL — Đọc trước khi disable password auth
LUÔN test SSH key trước khi disable password auth. Nếu key không hoạt động và bạn đã disable password — bạn bị khóa ngoài server. Không có cách nào quay lại trừ khi bạn có physical access hoặc cloud console.
Workflow an toàn:
- Mở 2 terminal — một đang SSH vào server (giữ session này)
- Terminal 2: thử SSH bằng key
- Nếu thành công → disable password auth
- Nếu fail → debug key/permission trên terminal 1 (vẫn đang connected)
Permission SSH — tại sao SSH kén permission?
bash
$ ssh deploy@production
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/deploy/.ssh/id_ed25519' are too open.SSH chủ động từ chối sử dụng key có permission quá rộng — vì nếu user khác trên cùng server đọc được private key, họ có thể impersonate bạn trên mọi server.
| File/Directory | Permission | Tại sao? |
|---|---|---|
~/.ssh/ | 700 | Chỉ owner truy cập thư mục |
~/.ssh/id_ed25519 | 600 | Private key — chỉ owner đọc |
~/.ssh/id_ed25519.pub | 644 | Public key — ai đọc cũng an toàn |
~/.ssh/authorized_keys | 600 | Danh sách key được phép login |
~/.ssh/config | 600 | Có thể chứa hostname, user, path |
2. SSH Config — Bí Quyết Productivity ⚡
Mỗi lần gõ ssh -i ~/.ssh/id_ed25519_prod -p 2222 deploy@203.0.113.10 là mỗi lần lãng phí thời gian và dễ gõ sai. SSH config biến tất cả thành một từ duy nhất.
Cấu trúc ~/.ssh/config
bash
# ~/.ssh/config — Cấu hình SSH cho toàn bộ infrastructure
# Production server
Host production
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519_prod
ServerAliveInterval 60
ConnectTimeout 5
# Staging server
Host staging
HostName 203.0.113.20
User deploy
IdentityFile ~/.ssh/id_ed25519_staging
# Database server (private network — cần jump qua production)
Host db-prod
HostName 10.0.1.50
User dbadmin
ProxyJump production # ← Jump through production
LocalForward 5433 localhost:5432 # ← Auto-tunnel PostgreSQL
# Wildcard — mọi server trong internal network
Host *.internal
ProxyJump production
User deploy
IdentityFile ~/.ssh/id_ed25519_internalTrước và sau SSH config
bash
# ❌ Before: nhớ IP, port, key path, username...
ssh -i ~/.ssh/id_ed25519_prod -p 2222 deploy@203.0.113.10
# ✅ After: một từ duy nhất
ssh production
# ❌ Before: multi-hop thủ công
ssh -i ~/.ssh/id_ed25519_prod -p 2222 deploy@203.0.113.10 \
-L 5433:10.0.1.50:5432
# ✅ After: ProxyJump + LocalForward tự động
ssh db-prod
# → Tự động jump qua production, tunnel PostgreSQL về localhost:5433💡 SSH config pro tips
- Wildcard patterns:
Host *.stagingáp dụng config cho mọi host kết thúc bằng.staging - Include:
Include ~/.ssh/config.d/*— tách config theo project/team - ServerAliveInterval 60: Gửi keepalive mỗi 60s, tránh bị disconnect khi idle
- ConnectTimeout 5: Timeout sau 5s thay vì chờ mặc định ~2 phút
- AddKeysToAgent yes: Tự động thêm key vào ssh-agent khi dùng lần đầu
3. SSH Tunneling — Đường Hầm Bí Mật 🚇
SSH tunneling cho phép bạn tạo kết nối encrypted để truy cập service nằm trong private network — không cần mở port trên firewall, không cần VPN. Có 3 loại tunnel.
3.1 Local Forward — Truy cập remote service từ laptop (phổ biến nhất)
Your Laptop Production Server Database
┌──────────────┐ ┌──────────────┐ ┌──────────┐
│ localhost:5433│───SSH───→ │ production │─────────→ │ db:5432 │
└──────────────┘ tunnel └──────────────┘ private └──────────┘
networkbash
# Access production PostgreSQL từ laptop
ssh -L 5433:db.internal:5432 production
# Giờ trên laptop:
psql -h localhost -p 5433 -U admin mydb
# → Traffic: laptop:5433 → SSH tunnel → production → db.internal:5432Use case thực tế:
- Truy cập database trong private subnet
- Dùng local IDE/tool kết nối remote service
- Access admin panel chỉ listen trên
127.0.0.1của server
3.2 Remote Forward — Expose local service cho remote server
Your Laptop Production Server
┌──────────────┐ ┌──────────────┐
│ localhost:3000│←───SSH─── │ server:8080 │
└──────────────┘ tunnel └──────────────┘bash
# Cho production server truy cập local dev server
ssh -R 8080:localhost:3000 production
# Trên production server:
curl http://localhost:8080
# → Traffic: production:8080 → SSH tunnel → laptop:3000Use case thực tế:
- Demo local development cho team (server truy cập được laptop)
- Debug webhook — cho external service gửi callback về local
- Tạm thời expose service khi không có VPN
💡 Penrift — Thay thế SSH remote forward
Nếu bạn cần expose local service ra Internet nhanh chóng, dùng Penrift Tunnel — đơn giản hơn SSH remote forward, không cần server trung gian, và có HTTPS tự động.
3.3 Dynamic Forward — SOCKS Proxy
bash
# Tạo SOCKS proxy qua production server
ssh -D 1080 production
# Configure browser/app dùng SOCKS5 proxy tại localhost:1080
# → Mọi traffic được route qua production server
# Hoặc dùng curl qua proxy:
curl --socks5 localhost:1080 http://internal-api.corp:8080/healthUse case thực tế:
- Truy cập internal web apps không có VPN
- Browse internal documentation/wiki
- Test xem service nhìn thế nào từ góc nhìn server
📋 Tổng hợp — Chọn đúng loại tunnel
| Loại | Lệnh | Hướng | Khi nào dùng? |
|---|---|---|---|
Local -L | ssh -L local:remote:port host | Laptop → Remote | Truy cập DB, admin panel từ laptop |
Remote -R | ssh -R remote:local:port host | Remote → Laptop | Demo, webhook debug |
Dynamic -D | ssh -D port host | SOCKS proxy | Browse internal network |
4. Firewall Basics — ufw 🧱
Firewall là lớp phòng thủ cuối cùng — ngay cả khi attacker biết IP server, nếu port đóng thì không thể kết nối. ufw (Uncomplicated Firewall) là frontend cho iptables, đơn giản hóa việc quản lý firewall trên Ubuntu/Debian.
Setup firewall cơ bản
bash
# Enable firewall (cẩn thận — đảm bảo allow SSH trước!)
sudo ufw allow 22/tcp # ← LUÔN allow SSH trước khi enable
sudo ufw enable
# Default policy: chặn mọi incoming, cho phép mọi outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow specific ports
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# Restrict port theo IP range — PostgreSQL chỉ từ internal network
sudo ufw allow from 10.0.0.0/24 to any port 5432
# Kiểm tra status
sudo ufw status verbose
# Output:
# Status: active
# To Action From
# -- ------ ----
# 22/tcp ALLOW Anywhere
# 80/tcp ALLOW Anywhere
# 443/tcp ALLOW Anywhere
# 5432 ALLOW 10.0.0.0/24
# Xóa rule
sudo ufw delete allow 80/tcpNguyên tắc cốt lõi: Least Privilege
Chỉ mở port thực sự cần thiết. Mỗi port mở là một attack surface mới.
| Port | Service | Ai cần truy cập? | Rule |
|---|---|---|---|
| 22 | SSH | Chỉ team SRE | allow from 10.0.0.0/24 to any port 22 |
| 80/443 | HTTP/HTTPS | Public | allow 80/tcp, allow 443/tcp |
| 5432 | PostgreSQL | Chỉ app servers | allow from 10.0.1.0/24 to any port 5432 |
| 6379 | Redis | Chỉ internal | allow from 10.0.0.0/24 to any port 6379 |
| 27017 | MongoDB | KHÔNG mở | Dùng SSH tunnel thay vì expose |
⚠️ Cạm bẫy
- ❌
ufw allowmọi thứ vì "cái gì đó không connect được" — debug bằngufw statusvà logs, đừng mở port bừa rồi quên đóng lại - ❌ Không biết Docker bypass ufw — Docker tự quản lý
iptablestrực tiếp,ufw denykhông chặn được port mà Docker expose. Phải dùng Docker network configuration hoặc bind container lên127.0.0.1thay vì0.0.0.0 - ❌ Cho phép port thay vì cho phép IP range —
ufw allow 5432mở PostgreSQL cho toàn bộ Internet. Luôn restrict bằng source IP:ufw allow from 10.0.0.0/24 to any port 5432
Kiểm tra nhanh: Nếu bạn chạy ufw status mà thấy hơn 10 rules ALLOW Anywhere — bạn đang làm sai.
5. 🔥 Production Incident — "Server Bị Hack Vì Password Auth"
🔥 Incident Report — Friday Night Crypto Mining
Thời điểm: Thứ Sáu, 23:00. Monitoring alert: CPU production server 100% liên tục 30 phút.
Triệu chứng:
- CPU usage 100% — không do application
- Process lạ chiếm toàn bộ resource
- SSH sessions từ IP không xác định
Investigation:
bash
# 1. Kiểm tra login history
$ last -20
deploy pts/3 45.xxx.xxx.xxx Fri Nov 15 22:31 still logged in
deploy pts/2 45.xxx.xxx.xxx Fri Nov 15 22:30 still logged in
deploy pts/1 203.0.113.10 Fri Nov 15 09:00 - 18:00 (09:00)
# ↑ IP 45.xxx.xxx.xxx — KHÔNG phải team chúng ta!
# 2. Đếm failed login attempts
$ journalctl -u sshd --since "1 week ago" | grep "Failed password" | wc -l
847293
# 💀 Gần 1 TRIỆU lần thử brute-force trong 1 tuần!
# 3. Kiểm tra login thành công
$ journalctl -u sshd | grep "Accepted password"
Nov 15 22:30:01 prod sshd[12345]: Accepted password for deploy from 45.xxx.xxx.xxx
# 💀 Attacker đoán được password
# 4. Process nào đang chạy?
$ ps aux | grep -v grep | sort -rn -k 3 | head -5
deploy 23456 99.0 2.1 xmrig --algo=rx/0 --url=stratum+tcp://pool.minexmr.com
# 💀 Crypto miner — server đang đào Monero cho hackerRoot cause analysis:
- ❌ Password authentication bật — cho phép brute-force
- ❌ Root login bật — target giá trị cao nhất
- ❌ Password yếu —
admin123bị crack sau vài giờ brute-force - ❌ Không có fail2ban — không giới hạn login attempts
- ❌ SSH trên port 22 mặc định — bot quét tự động tìm thấy ngay
- ❌ Không có firewall restrict SSH source IP
Emergency response:
bash
# 1. Kill sessions lạ NGAY LẬP TỨC
who | grep "45.xxx"
# deploy pts/3 45.xxx.xxx.xxx
sudo kill -9 $(who | grep "45.xxx" | awk '{print $2}' | xargs -I{} fuser /dev/{} 2>/dev/null)
# 2. Disable password auth — chặn brute-force
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# 3. Kill crypto miner
sudo kill -9 $(pgrep xmrig)
# 4. Kiểm tra backdoors
echo "=== Checking crontabs ==="
crontab -l # Crontab của user hiện tại
sudo crontab -l # Crontab của root
cat /etc/crontab # System crontab
echo "=== Checking suspicious files ==="
ls -la /tmp/ /var/tmp/ /dev/shm/ # Nơi malware hay ẩn
find / -name "*.sh" -newer /etc/passwd -ls # Script tạo gần đây
echo "=== Checking authorized_keys ==="
cat ~/.ssh/authorized_keys # Attacker có thể đã thêm key
# ← Xóa mọi key không phải của team bạn!
echo "=== Checking for modified binaries ==="
debsums -c 2>/dev/null | head -20 # Tìm system binary bị thay đổiKết quả: Attacker đã thêm crontab chạy crypto miner mỗi 5 phút và thêm SSH key vào authorized_keys. Sau khi cleanup, team chuyển sang key-based auth, cài fail2ban, và restrict SSH bằng firewall.
Prevention Checklist — Không Bao Giờ Lặp Lại
✅ Checklist triển khai
- [ ] Disable password authentication —
PasswordAuthentication notrongsshd_config - [ ] Disable root login —
PermitRootLogin no - [ ] Sử dụng Ed25519 keys — mạnh hơn RSA, compact hơn
- [ ] Đổi SSH port — từ 22 sang port khác (giảm bot scan, không phải security measure chính)
- [ ] Cài fail2ban — tự động block IP sau N lần login fail
- [ ] Firewall restrict SSH source IP — chỉ cho phép từ office/VPN IP range
- [ ] Enable 2FA — cài
google-authenticatorhoặcduocho SSH - [ ] Key rotation — đổi key định kỳ, xóa key của người đã rời team
- [ ] Audit authorized_keys — review thường xuyên, tự động hóa bằng script
- [ ] Monitor SSH logs — alert khi có login từ IP lạ hoặc số failed attempt bất thường
6. Bài Tập Nhanh — Thực Hành SSH 🏋️
Bài 1: Viết SSH config cho multi-hop connection
Yêu cầu: Bạn có infrastructure sau:
- Bastion host (jump box):
bastion.example.com, port 22, usersre - App server:
10.0.1.100(private network), userdeploy - Database:
10.0.2.50(private network), userdbadmin, PostgreSQL port 5432
Viết ~/.ssh/config để:
ssh bastion→ kết nối bastionssh app-prod→ jump qua bastion vào app serverssh db-prod→ jump qua bastion vào DB server + auto-tunnel PostgreSQL vềlocalhost:5433
💡 Gợi ý giải pháp
bash
# ~/.ssh/config
Host bastion
HostName bastion.example.com
User sre
IdentityFile ~/.ssh/id_ed25519_bastion
ServerAliveInterval 60
Host app-prod
HostName 10.0.1.100
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_deploy
Host db-prod
HostName 10.0.2.50
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_db
LocalForward 5433 localhost:5432Test: ssh db-prod → tự động jump qua bastion, tunnel PostgreSQL. Trên laptop: psql -h localhost -p 5433.
Bài 2: Spot the Bug — Tìm lỗi bảo mật trong sshd_config
bash
# /etc/ssh/sshd_config
Port 22 # ← ?
PermitRootLogin yes # ← ?
PasswordAuthentication yes # ← ?
PubkeyAuthentication yes
MaxAuthTries 100 # ← ?
AllowUsers root admin deploy # ← ?
X11Forwarding yes # ← ?Câu hỏi: Tìm ít nhất 5 vấn đề bảo mật trong config trên.
💡 Đáp án
- Port 22 — Port mặc định, bị bot quét liên tục. Nên đổi sang port khác (vd: 2222)
- PermitRootLogin yes — Cho phép login root qua SSH. LUÔN set
no - PasswordAuthentication yes — Cho phép brute-force. Set
nosau khi setup key auth - MaxAuthTries 100 — Cho phép 100 lần thử! Mặc định 6 đã quá nhiều. Set 3-5
- AllowUsers chứa root — Ngay cả khi
PermitRootLogin no,AllowUsers rootvẫn confusing. Xóarootkhỏi danh sách - X11Forwarding yes — Không cần trên server. Tắt để giảm attack surface
Config đã fix:
bash
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers deploy
X11Forwarding no7. Anti-Pattern — "The Shared SSH Key" 🚫
⚠️ Cạm bẫy
Tình huống: Để "tiện", team tạo một key pair duy nhất và share private key cho tất cả 10 người qua Slack/email.
Tại sao đây là thảm họa:
| Vấn đề | Hậu quả |
|---|---|
| Không có audit trail | Ai SSH vào server lúc 3 AM? Không biết — tất cả dùng cùng key |
| Người rời team | Phải rotate key trên MỌI server và gửi key mới cho cả team |
| Key bị leak | Một máy bị compromise → MỌI server bị compromise |
| Compliance | Audit fails — không chứng minh được ai truy cập gì |
The fix — One key per person:
bash
# Mỗi engineer tạo key riêng
# Alice:
ssh-keygen -t ed25519 -C "alice@company.com"
# Bob:
ssh-keygen -t ed25519 -C "bob@company.com"
# Trên server — authorized_keys chứa key của từng người:
cat ~/.ssh/authorized_keys
# ssh-ed25519 AAAA...alice alice@company.com
# ssh-ed25519 AAAA...bob bob@company.com
# Alice rời team? Xóa MỘT dòng:
sed -i '/alice@company.com/d' ~/.ssh/authorized_keys
# → Không ảnh hưởng ai khácProduction-grade solution: Dùng SSH Certificate Authority (CA) — issue short-lived certificates thay vì quản lý authorized_keys thủ công. Các công ty lớn (Google, Facebook, Netflix) đều dùng SSH CA.
💡 Performance & Reliability trong scripts
Dùng ssh -o ConnectTimeout=5 trong scripts để tránh hang vô hạn khi server không phản hồi. Trong SSH config:
bash
# ~/.ssh/config — global defaults
Host *
ConnectTimeout 5 # Timeout kết nối sau 5s
ServerAliveInterval 60 # Keepalive mỗi 60s
ServerAliveCountMax 3 # Disconnect sau 3 lần không phản hồi (= 3 phút)
AddKeysToAgent yes # Tự thêm key vào agent
IdentitiesOnly yes # Chỉ dùng key được chỉ định, không thử tất cảĐặc biệt quan trọng trong CI/CD pipelines và health check scripts — một SSH hang có thể block cả pipeline.
8. Quiz — Kiểm Tra Kiến Thức
🧠 Quiz
Câu 1: Tại sao SSH từ chối sử dụng private key có permission 644?
- [ ] A) Vì SSH protocol yêu cầu permission cụ thể để encrypt
- [ ] B) Vì performance — permission chặt giúp đọc file nhanh hơn
- [x] C) Vì security — nếu user khác đọc được private key, họ có thể impersonate bạn trên mọi server
- [ ] D) Vì Linux kernel chỉ cho phép SSH đọc file với permission 600
💡 Giải thích: Private key là credential — tương đương password. Permission
644cho phép mọi user trên server đọc key. SSH client chủ động từ chối sử dụng key quá "mở" để bảo vệ bạn. Fix:chmod 600 ~/.ssh/id_ed25519.
Câu 2: Lệnh ssh -L 5433:db.internal:5432 production làm gì?
- [ ] A) Mở port 5433 trên production server
- [ ] B) Forward traffic từ production:5433 về laptop:5432
- [x] C) Forward traffic từ laptop:5433 qua SSH tunnel tới db.internal:5432
- [ ] D) Tạo SOCKS proxy trên port 5433
💡 Giải thích:
-Llà Local Forward — lắng nghe trênlocalhost:5433của laptop, tunnel qua SSH tới production, rồi production kết nối tớidb.internal:5432. Kết quả:psql -h localhost -p 5433trên laptop kết nối tới database trong private network.
Câu 3: Docker container expose port 6379 (Redis). Bạn chạy sudo ufw deny 6379. Redis có bị chặn từ bên ngoài không?
- [ ] A) Có — ufw chặn mọi traffic tới port 6379
- [x] B) Không — Docker bypass ufw vì Docker tự quản lý iptables trực tiếp
- [ ] C) Có — nhưng chỉ khi Docker dùng bridge network
- [ ] D) Không — vì Redis không dùng TCP
💡 Giải thích: Docker trực tiếp thao tác iptables, bypass hoàn toàn ufw rules.
ufw deny 6379không chặn được traffic tới Docker container. Fix: bind container lên127.0.0.1thay vì0.0.0.0(docker run -p 127.0.0.1:6379:6379 redis), hoặc dùng Docker network configuration.