Skip to content

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 ufw theo 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 AuthSSH Key Auth
Brute-force resistanceP@ssw0rd123 → crack trong vài giờEd25519/RSA-4096 → không thể brute-force
Intercept riskPassword truyền qua network (dù encrypted)Private key không bao giờ rời máy bạn
Per-host controlCùng password cho mọi server? 🤦Mỗi server một key riêng, restrict per-command
AutomationScript nhập password = nightmareCI/CD, deploy script dùng key tự nhiên
RevocationĐổi password → thông báo cả teamXó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:

  1. Mở 2 terminal — một đang SSH vào server (giữ session này)
  2. Terminal 2: thử SSH bằng key
  3. Nếu thành công → disable password auth
  4. 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/DirectoryPermissionTại sao?
~/.ssh/700Chỉ owner truy cập thư mục
~/.ssh/id_ed25519600Private key — chỉ owner đọc
~/.ssh/id_ed25519.pub644Public key — ai đọc cũng an toàn
~/.ssh/authorized_keys600Danh sách key được phép login
~/.ssh/config600Có 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_internal

Trướ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  └──────────┘
                                               network
bash
# 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:5432

Use 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.1 củ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:3000

Use 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/health

Use 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ạiLệnhHướngKhi nào dùng?
Local -Lssh -L local:remote:port hostLaptop → RemoteTruy cập DB, admin panel từ laptop
Remote -Rssh -R remote:local:port hostRemote → LaptopDemo, webhook debug
Dynamic -Dssh -D port hostSOCKS proxyBrowse 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/tcp

Nguyê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.

PortServiceAi cần truy cập?Rule
22SSHChỉ team SREallow from 10.0.0.0/24 to any port 22
80/443HTTP/HTTPSPublicallow 80/tcp, allow 443/tcp
5432PostgreSQLChỉ app serversallow from 10.0.1.0/24 to any port 5432
6379RedisChỉ internalallow from 10.0.0.0/24 to any port 6379
27017MongoDBKHÔNG mởDùng SSH tunnel thay vì expose

⚠️ Cạm bẫy

  1. ufw allow mọi thứ vì "cái gì đó không connect được" — debug bằng ufw status và logs, đừng mở port bừa rồi quên đóng lại
  2. Không biết Docker bypass ufw — Docker tự quản lý iptables trực tiếp, ufw deny không chặn được port mà Docker expose. Phải dùng Docker network configuration hoặc bind container lên 127.0.0.1 thay vì 0.0.0.0
  3. Cho phép port thay vì cho phép IP rangeufw allow 5432 mở 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 hacker

Root cause analysis:

  • ❌ Password authentication bật — cho phép brute-force
  • ❌ Root login bật — target giá trị cao nhất
  • ❌ Password yếu — admin123 bị 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 đổi

Kế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 authenticationPasswordAuthentication no trong sshd_config
  • [ ] Disable root loginPermitRootLogin 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-authenticator hoặc duo cho 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, user sre
  • App server: 10.0.1.100 (private network), user deploy
  • Database: 10.0.2.50 (private network), user dbadmin, PostgreSQL port 5432

Viết ~/.ssh/config để:

  1. ssh bastion → kết nối bastion
  2. ssh app-prod → jump qua bastion vào app server
  3. ssh 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:5432

Test: 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
  1. Port 22 — Port mặc định, bị bot quét liên tục. Nên đổi sang port khác (vd: 2222)
  2. PermitRootLogin yes — Cho phép login root qua SSH. LUÔN set no
  3. PasswordAuthentication yes — Cho phép brute-force. Set no sau khi setup key auth
  4. MaxAuthTries 100 — Cho phép 100 lần thử! Mặc định 6 đã quá nhiều. Set 3-5
  5. AllowUsers chứa root — Ngay cả khi PermitRootLogin no, AllowUsers root vẫn confusing. Xóa root khỏi danh sách
  6. 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 no

7. 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 trailAi SSH vào server lúc 3 AM? Không biết — tất cả dùng cùng key
Người rời teamPhải rotate key trên MỌI server và gửi key mới cho cả team
Key bị leakMột máy bị compromise → MỌI server bị compromise
ComplianceAudit 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ác

Production-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 pipelineshealth 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 644 cho 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: -LLocal Forward — lắng nghe trên localhost:5433 của laptop, tunnel qua SSH tới production, rồi production kết nối tới db.internal:5432. Kết quả: psql -h localhost -p 5433 trê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 6379 không chặn được traffic tới Docker container. Fix: bind container lên 127.0.0.1 thay vì 0.0.0.0 (docker run -p 127.0.0.1:6379:6379 redis), hoặc dùng Docker network configuration.