Vấn đề với TLS nội bộ: Tự ký loạn xạ và chứng chỉ hết hạn
Sau khi máy chủ của tôi bị tấn công brute-force SSH lúc nửa đêm, tôi trở nên ám ảnh với việc khóa chặt mọi bề mặt tấn công mà mình đã bỏ qua. Sự cố đó dạy tôi một bài học xương máu: firewall và fail2ban bảo vệ được vòng ngoài, nhưng traffic nội bộ vẫn đang chạy dạng plaintext — credentials, session token, tất cả đều lộ thiên.
Hầu hết các nhóm đều chọn một trong hai hướng xử lý TLS nội bộ: bỏ qua mã hóa hoàn toàn và tin tưởng mạng nội bộ sẽ sạch, hoặc rải chứng chỉ tự ký (self-signed) khắp nơi. Cả hai trông có vẻ là giải pháp, nhưng cả hai đều tích lũy vấn đề theo thời gian.
Bỏ qua TLS nội bộ đồng nghĩa chỉ cần một host bị xâm phạm là kẻ tấn công có thể bắt toàn bộ credentials plaintext trên service mesh của bạn. Dùng cert tự ký thì bạn phải đối mặt với cảnh báo trình duyệt liên miên, chạy đôn chạy đáo theo dõi chứng chỉ sắp hết hạn trước khi dịch vụ sập, và từ từ huấn luyện cả team thói quen click qua cảnh báo bảo mật mà không thèm đọc.
Căn nguyên của vấn đề: không có Certificate Authority đáng tin cậy cho hạ tầng nội bộ. Các CA công cộng như Let’s Encrypt chỉ cấp chứng chỉ cho tên miền có thể phân giải công khai. Các hostname nội bộ của bạn — api.internal, db.prod.local, vault.homelab — sẽ không bao giờ đủ điều kiện.
Step-CA lấp đầy khoảng trống đó. Đây là Certificate Authority nội bộ mã nguồn mở hỗ trợ ACME — giao thức tương tự mà Let’s Encrypt sử dụng. Các ACME client hiện có của bạn (certbot, acme.sh, Caddy) chỉ cần trỏ vào CA nội bộ này thay vì CA công cộng, không cần thay đổi code. Chứng chỉ được cấp tự động và gia hạn trước khi hết hạn, cho bất kỳ hostname nào bạn kiểm soát.
Các khái niệm cốt lõi trước khi cài đặt
Step-CA là gì
Step-CA (từ Smallstep) là một CA server chạy trên hạ tầng của chính bạn. Nó quản lý chứng chỉ root CA và cấp chứng chỉ leaf cho các client xác thực đúng cách. Khác với các enterprise CA truyền thống như Microsoft ADCS, Step-CA được xây dựng cho các nhóm nhỏ và tích hợp tốt với các công cụ DevOps hiện đại. Toàn bộ server được đóng gói thành một binary duy nhất — không cần database, không có external dependency.
Giao thức ACME — Động cơ đằng sau Let’s Encrypt
ACME (Automated Certificate Management Environment, RFC 8555) là giao thức đã phổ cập TLS tự động và miễn phí. Client chứng minh quyền kiểm soát domain thông qua một challenge — HTTP-01, DNS-01, hoặc TLS-ALPN-01 — và CA cấp chứng chỉ. Với CA nội bộ, bạn kiểm soát cả hai phía. Điều đó có nghĩa là quy tắc xác thực cho hostname nội bộ hoàn toàn do bạn cấu hình.
Chuỗi tin cậy (Trust Chain)
Để chứng chỉ Step-CA được tin cậy mà không hiện cảnh báo trình duyệt, bạn chỉ cần cài chứng chỉ root CA của Step-CA vào OS trust store một lần duy nhất trên mỗi máy. Chỉ vậy thôi. Mọi chứng chỉ CA cấp về sau đều được tự động tin cậy trên máy đó — không cần cấu hình thêm gì cho từng cert.
Thực hành: Triển khai Step-CA với ACME
Bước 1: Cài đặt Step-CA và step CLI
Step-CA được phân phối dưới dạng binary đơn. Trên Debian/Ubuntu:
# Cài đặt step CLI (client quản lý chứng chỉ)
wget https://dl.smallstep.com/cli/docs-cli-install/latest/step-cli_amd64.deb
sudo dpkg -i step-cli_amd64.deb
# Cài đặt step-ca server
wget https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_amd64.deb
sudo dpkg -i step-ca_amd64.deb
# Kiểm tra phiên bản
step version
step-ca version
Trên RHEL/Rocky/AlmaLinux, tải các gói RPM từ trang GitHub releases của Smallstep.
Bước 2: Khởi tạo CA nội bộ
Tạo system user riêng và khởi tạo CA trong thư mục home của user đó:
sudo useradd --system --home /etc/step-ca --shell /bin/false step
sudo mkdir -p /etc/step-ca
sudo chown step:step /etc/step-ca
# Khởi tạo CA với quyền của user step
sudo -u step env STEPPATH=/etc/step-ca step ca init \
--name="My Internal CA" \
--dns="ca.internal,ca.yourdomain.local" \
--address=":9000" \
--provisioner="[email protected]"
Lệnh này tạo chứng chỉ root CA và intermediate CA trong /etc/step-ca/certs/, và private key trong /etc/step-ca/secrets/. Bạn sẽ được nhắc đặt mật khẩu cho private key của CA. Hãy lưu mật khẩu này ở nơi an toàn — HashiCorp Vault, AWS Secrets Manager, hoặc ít nhất là file có phân quyền 0400 trên CA host mà chỉ user step mới đọc được.
Bước 3: Thêm ACME Provisioner
Step-CA mặc định dùng JWK provisioner. Thêm ACME provisioner để các ACME client tiêu chuẩn có thể yêu cầu chứng chỉ:
sudo -u step env STEPPATH=/etc/step-ca \
step ca provisioner add acme --type ACME
Kiểm tra /etc/step-ca/config/ca.json sau khi chạy lệnh này — bạn sẽ thấy một entry có "type": "ACME" trong mảng provisioners. URL thư mục ACME sẽ là https://ca.internal:9000/acme/acme/directory.
Bước 4: Chạy Step-CA như một systemd Service
cat <<EOF | sudo tee /etc/systemd/system/step-ca.service
[Unit]
Description=step-ca Private Certificate Authority
After=network.target
[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
ExecStart=/usr/bin/step-ca /etc/step-ca/config/ca.json --password-file /etc/step-ca/password.txt
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now step-ca
sudo systemctl status step-ca
Lưu mật khẩu CA vào /etc/step-ca/password.txt và khóa chặt phân quyền:
sudo chmod 600 /etc/step-ca/password.txt
sudo chown step:step /etc/step-ca/password.txt
Bước 5: Bootstrap tin cậy cho Client
Mỗi máy cần tin cậy CA của bạn phải cài chứng chỉ root. CLI step xử lý việc này trong vài giây:
# Lấy fingerprint của CA (chạy một lần trên CA server)
step certificate fingerprint /etc/step-ca/certs/root_ca.crt
# Trên mỗi máy client — bootstrap trust
step ca bootstrap \
--ca-url https://ca.internal:9000 \
--fingerprint YOUR_FINGERPRINT_HERE
# Cài chứng chỉ root vào system trust store
sudo step certificate install $(step path)/certs/root_ca.crt
Đẩy fingerprint xuống các máy client qua Ansible, Salt, hoặc Puppet như một tác vụ bootstrap một lần. Sau khi chạy xong, máy đó tin cậy mọi chứng chỉ CA cấp — kể cả những cert được cấp nhiều tháng sau — mà không cần đụng vào nữa.
Bước 6: Cấp chứng chỉ qua ACME
Bất kỳ ACME client nào cũng có thể trỏ vào endpoint CA nội bộ của bạn. Với certbot — đảm bảo đã hoàn thành Bước 5 trước, vì certbot xác thực chứng chỉ TLS của ACME server dựa trên system trust store:
certbot certonly \
--standalone \
--server https://ca.internal:9000/acme/acme/directory \
--domain api.internal \
--email [email protected] \
--agree-tos
Nếu certbot không kết nối được đến thư mục ACME, nguyên nhân phổ biến nhất là chứng chỉ root chưa được cài vào system store. Truyền thẳng bằng REQUESTS_CA_BUNDLE:
REQUESTS_CA_BUNDLE=/etc/step-ca/certs/root_ca.crt certbot certonly \
--standalone \
--server https://ca.internal:9000/acme/acme/directory \
--domain api.internal \
--email [email protected] \
--agree-tos
Với acme.sh (hữu ích khi certbot không khả dụng hoặc bạn đang script hóa việc cấp cert):
acme.sh --issue \
--server https://ca.internal:9000/acme/acme/directory \
--domain api.internal \
--standalone \
--ca-bundle /etc/step-ca/certs/root_ca.crt
Người dùng Caddy được xử lý tự động — đặt acme_ca trong Caddyfile trỏ đến URL thư mục Step-CA và Caddy lo phần còn lại.
Bước 7: Cấu hình thời hạn và gia hạn chứng chỉ
Step-CA mặc định cấp chứng chỉ ACME với thời hạn 24 giờ. Ngắn là có chủ đích: buộc tự động hóa ngay từ ngày đầu và giới hạn thiệt hại nếu private key bị lộ — một cert bị rò rỉ hết hạn sau 24 giờ gây hại ít hơn rất nhiều so với cert có hiệu lực cả năm. Điều chỉnh claims của ACME provisioner trong ca.json cho phù hợp với chu kỳ gia hạn của bạn:
{
"type": "ACME",
"name": "acme",
"claims": {
"minTLSCertDuration": "24h",
"maxTLSCertDuration": "720h",
"defaultTLSCertDuration": "168h"
}
}
systemd timer của certbot xử lý gia hạn tự động. Nếu dùng trực tiếp CLI step, chế độ daemon renew giữ chứng chỉ luôn tươi mà không cần cron job:
# Chạy dưới dạng daemon nền — chủ động gia hạn trước khi hết hạn
step ca renew --daemon \
/etc/ssl/private/api.internal.crt \
/etc/ssl/private/api.internal.key
Kiểm tra
# Xem thông tin chứng chỉ đã cấp
step certificate inspect /etc/letsencrypt/live/api.internal/cert.pem
# Kiểm tra TLS từ client đã bootstrap trust
curl https://api.internal
# Xác minh chuỗi chứng chỉ dựa trên root CA
openssl verify -CAfile /etc/step-ca/certs/root_ca.crt \
/etc/letsencrypt/live/api.internal/cert.pem
Các bước tiếp theo
Bạn đã có một CA nội bộ được hạ tầng của mình tin cậy, tự động cấp và gia hạn chứng chỉ TLS thông qua ACME chuẩn. Không còn cảnh báo cert tự ký. Không còn dịch vụ sập vì ai đó quên gia hạn cert. Không còn lý do biện hộ kiểu “traffic nội bộ thôi mà” để bào chữa cho giao tiếp plaintext.
Một số việc cần xử lý trước khi triển khai rộng rãi: sao lưu private key của root CA offline và ở nhiều nơi khác nhau — mất nó là phải cấp lại toàn bộ chứng chỉ trong hạ tầng từ đầu. Để đảm bảo tính sẵn sàng cao, chạy một instance Step-CA thứ cấp dùng chung intermediate CA. Và kiểm tra xem máy nào đã bootstrap trust trước khi tắt CA host, không thì bạn sẽ có một buổi chiều sôi động chạy đi chữa TLS error khắp fleet.
Ngoài những tính năng cơ bản, Step-CA còn hỗ trợ OIDC provisioner (chứng chỉ gắn với danh tính Google hoặc GitHub), chế độ SSH certificate authority (chứng chỉ SSH ngắn hạn thay thế quản lý static key), và lưu trữ hardware key qua PKCS#11. Đáng khám phá sau khi nền tảng ACME đã chạy ổn định — mỗi tính năng giải quyết một vấn đề thực tế mà không tốn thêm chi phí vận hành khi bạn chưa cần đến.

