Hướng dẫn Setup Jenkins cho hệ thống Monolith + Bài học 39 + Bảo mật hệ thống Jenkins (Jenkins Hardening): Cấu hình HTTPS, Reverse Proxy, CSRF và vá lỗ hổng bảo mật
Ở Bài 28, chúng ta đã thiết lập lớp bảo vệ bên trong bằng cơ chế phân quyền RBAC. Tuy nhiên, nếu bạn vẫn đang truy cập vào Jenkins qua địa chỉ http://<IP_SERVER>:8080 thuần túy mà không có mã hóa SSL/TLS, hoặc cho phép lập trình viên tự do sửa tệp Jenkinsfile trên nhánh feature/* để chạy những đoạn mã Groovy tùy ý, hệ thống CI/CD của bạn đang đứng trước những lỗ hổng an ninh chí mạng.
Hãy nhớ rằng: Ai chiếm được quyền điều khiển máy chủ Jenkins Master, người đó mặc nhiên chiếm được toàn bộ máy chủ Production, Database và mã nguồn của khối Monolith! Bài học hôm nay sẽ hướng dẫn bạn gia cố pháo đài Jenkins (Jenkins Hardening) từ tầng mạng (Nginx Reverse Proxy & HTTPS) cho đến tầng lõi thực thi (Groovy Sandbox, Agent-to-Controller Isolation và CSRF Protection).
1. Năm bề mặt tấn công (Attack Surfaces) nguy hiểm nhất trên Jenkins
Trước khi bắt tay vào cấu hình, hãy điểm qua 5 con đường phổ biến nhất mà tin tặc (hoặc một tài khoản nội bộ bị lộ) thường lợi dụng để tấn công máy chủ Jenkins của dự án Monolith:
| Bề mặt tấn công | Kịch bản khai thác thực tế | Hậu quả đối với hệ thống Monolith |
|---|---|---|
1. Nghe lén đường truyền (Plaintext HTTP :8080) |
Quản trị viên đăng nhập Jenkins hoặc GitLab bắn Webhook qua giao thức http:// không mã hóa. |
Lộ mật khẩu Admin, lộ Session Cookie hoặc bị đánh cắp khóa bí mật ngay trên đường truyền mạng. |
| 2. Tấn công qua Jenkinsfile độc hại (Pipeline Poisoning) | Một lập trình viên đẩy nhánh feature/hack chứa lệnh Groovy Jenkins.instance.credentials... hoặc dùng sh để in trộm khóa SSH của Production. |
Toàn bộ mật khẩu Production bị in ra Console Log dù lập trình viên đó không hề có quyền xem trên UI! |
| 3. Giả mạo yêu cầu liên trang (CSRF) & Lộ Endpoint nhạy cảm | Kẻ tấn công lừa Admin bấm vào một đường link chứa lệnh gọi ngầm tới /script (Script Console) hoặc /prometheus. |
Thực thi mã từ xa (RCE - Remote Code Execution) chiếm quyền điều khiển máy chủ Host. |
| 4. Máy chủ Agent tấn công ngược lên Master (Agent-to-Controller) | Máy chủ Worker Agent bị nhiễm mã độc khi chạy một thư viện bên thứ 3, sau đó gửi lệnh điều khiển ngược về Jenkins Master. | Từ một máy Worker phụ bị nhiễm độc, tin tặc leo thang đặc quyền chiếm luôn não bộ Jenkins Master. |
5. Chạy Container Jenkins dưới quyền root + docker.sock |
Việc mount /var/run/docker.sock tương đương với việc trao quyền root của máy chủ vật lý cho container. |
Chỉ cần thoát khỏi container (Container Escape), kẻ tấn công sẽ làm chủ hoàn toàn hệ điều hành Linux bên dưới. |
2. Lớp giáp 1: Đóng cổng 8080 và bọc Jenkins sau Nginx Reverse Proxy + SSL/TLS (HTTPS)
Nguyên tắc đầu tiên khi đưa Jenkins vào vận hành chính thức là: Tuyệt đối không bao giờ phơi trực tiếp cổng 8080 của Java (Jetty Web Server) ra mạng Internet!
Chúng ta sẽ khóa cổng 8080 chỉ cho phép lắng nghe nội bộ (127.0.0.1:8080), và đặt Nginx Reverse Proxy đứng chắn phía trước tại cổng 443 (HTTPS) để gánh toàn bộ việc giải mã chứng chỉ SSL, chống tấn công DDoS cơ bản và lọc bỏ các đường dẫn nhạy cảm.
Bước 1: Khóa cổng 8080 nội bộ trong docker-compose.yml của Jenkins Master
Mở tệp docker-compose.yml của Jenkins Master (Bài 34) và sửa phần ports:
YAML
services:
jenkins-master:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins-master
ports:
# CHỈ lắng nghe ở giao diện Loopback nội bộ (Người ngoài quét IP:8080 sẽ bị từ chối hoàn toàn!)
- "127.0.0.1:8080:8080"
# Cổng 50000 chỉ cần mở nếu bạn dùng JNLP Agent; nếu dùng SSH Agent (Bài 31) thì đóng luôn cổng 50000!
Bước 2: Cấu hình Nginx Reverse Proxy chuẩn Hardening cho Jenkins
Tạo tệp /etc/nginx/sites-available/jenkins-secure.conf trên máy chủ Host (đã cài đặt chứng chỉ SSL miễn phí bằng Let's Encrypt Certbot hoặc chứng chỉ nội bộ):
Nginx
upstream jenkins_controller {
keepalive 32;
server 127.0.0.1:8080 fail_timeout=0;
}
# Map hỗ trợ nâng cấp kết nối WebSocket (Bắt buộc cho Jenkins CLI và Blue Ocean / Stage View live logs)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name jenkins.yourcompany.com;
# Ép buộc chuyển hướng 100% HTTP sang HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name jenkins.yourcompany.com;
# 1. Cấu hình chứng chỉ SSL/TLS chuẩn hiện đại (Chỉ cho phép TLS 1.2 và TLS 1.3)
ssl_certificate /etc/letsencrypt/live/jenkins.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jenkins.yourcompany.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 2. Bổ sung các HTTP Security Headers chống Clickjacking và XSS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
# 3. CHỐT CHẶN BẢO MẬT ĐẮT GIÁ: Chỉ cho phép dải IP VPN/Văn phòng công ty truy cập vào /script (Script Console) và /prometheus!
location ~* ^/(script|prometheus|manage) {
allow 10.0.0.0/8; # Cho phép mạng LAN / VPN nội bộ
allow 118.69.123.45/32; # Cho phép IP tĩnh văn phòng công ty
deny all; # Chặn đứng mọi truy cập từ Internet công cộng vào trang quản trị!
proxy_pass http://jenkins_controller;
include /etc/nginx/proxy_params;
}
# 4. Cấu hình chuyển tiếp chuẩn cho Jenkins (Chống lỗi "Reverse Proxy Setup is Broken")
ignore_invalid_headers off;
client_max_body_size 50m;
location / {
proxy_pass http://jenkins_controller;
proxy_redirect http:// https://;
proxy_http_version 1.1;
# Hỗ trợ WebSocket cho Live Console Log
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 4 Header bắt buộc phải có để Jenkins nhận diện đúng HTTPS và chống lỗi CSRF Crumb!
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_request_buffering off;
}
}
Giải thích lỗi kinh điển
"It appears that your reverse proxy set up is broken":Khi mới đặt Nginx trước Jenkins, rất nhiều người gặp cảnh báo màu đỏ này trong Manage Jenkins. Nguyên nhân là do Nginx thiếu 2 header
X-Forwarded-Proto $scheme;vàX-Forwarded-Port 443;, hoặc trong mục Manage Jenkins -> System -> Jenkins URL bạn vẫn để làhttp://...thay vì sửa thành[https://jenkins.yourcompany.com/](https://jenkins.yourcompany.com/). Khi cả Nginx và Jenkins URL cùng khớphttps://, cảnh báo sẽ biến mất hoàn toàn!
3. Lớp giáp 2: Bật bảo vệ CSRF (Crumb Issuer) và Phân quyền Thư mục Credentials
A. Chống giả mạo yêu cầu với Strict Crumb Issuer (CSRF Protection)
Mặc định Jenkins sử dụng một mã token dùng 1 lần gọi là Jenkins-Crumb để bảo vệ mọi thao tác POST (như bấm Build, xóa Job, sửa cấu hình). Tuy nhiên, để nâng mức bảo vệ lên tối đa (kiểm tra khớp cả địa chỉ IP của phiên đăng nhập), bạn hãy:
-
Cài đặt plugin Strict Crumb Issuer.
-
Vào Manage Jenkins -> Security -> Tại mục CSRF Protection, chọn Strict Crumb Issuer -> Bấm vào Advanced và tích chọn Check the session ID (Khóa chặt mã Crumb với đúng Session đăng nhập hiện tại).
B. Cô lập Kho khóa bí mật của Production khỏi nhánh feature/* (Folder-level Credentials)
Hãy tưởng tượng tình huống này: Ở Bài 9 và Bài 24, bạn lưu khóa monolith-env-prod và SSH Key của Production ở phạm vi Global Credentials (Toàn cục).
-
Nếu một lập trình viên mở nhánh
feature/test-code, tự tay sửa tệpJenkinsfiletrên nhánh đó thành:Groovy
withCredentials([file(credentialsId: 'monolith-env-prod', variable: 'HACK')]) { sh 'cat $HACK | base64' // Mã hóa base64 để qua mặt bộ lọc dấu *** của Jenkins! } -
Do
monolith-env-prodđang để ở phạm vi Global, bất kỳ Job nào trên Jenkins cũng có quyền gọiwithCredentialstới ID đó!
Giải pháp khóa chặt triệt để:
-
Sử dụng Folders Plugin tạo ra 2 thư mục tách biệt hoàn toàn trên trang chủ Jenkins:
-
Thư mục 1:
Monolith-CI-and-Staging(Nơi chứa Multibranch Pipeline quét các nhánhfeature/*,develop,staging). -
Thư mục 2:
Monolith-Production-Release(Chỉ chứa đúng Job chạy từ nhánhmainđã được bảo vệ nghiêm ngặt trên GitLab/GitHub).
-
-
Tuyệt đối không lưu mật khẩu Production ở Global Credentials! Hãy bấm vào bên trong thư mục
Monolith-Production-Release-> Chọn menu Credentials ở cột bên trái (chỉ thuộc phạm vi Folder này) -> Lưumonolith-env-prodvàprod-ssh-keyvào đây! -
Kết quả: Các nhánh
feature/*nằm ở thư mụcMonolith-CI-and-Staginghoàn toàn mù tịt và không bao giờ có thể truy xuất được tới khóa của thư mụcMonolith-Production-Release!
4. Lớp giáp 3: Kiểm soát In-process Script Approval & Groovy Sandbox
Khi chạy Pipeline, Jenkins cung cấp 2 chế độ thực thi mã Groovy:
-
Groovy Sandbox (Hộp cát an toàn): Bật mặc định cho mọi
Jenkinsfilelấy từ Git. Trong chế độ này, các câu lệnh nguy hiểm của Java nhưSystem.exit(0),new File('/etc/passwd').texthoặcJenkins.instance...sẽ bị chặn đứng ngay lập tức! -
In-process Script Approval (
Manage Jenkins -> In-process Script Approval): Khi một đoạn code vượt rào bị chặn, Jenkins sẽ dừng bản build và yêu cầu Admin vào trang In-process Script Approval bấm Approve.
Quy tắc sinh tử cho Admin:
-
Tuyệt đối không bao giờ bấm nút
Approvemột cách mù quáng đối với các chữ ký hàm nhạy cảm nhưstaticMethod jenkins.model.Jenkins getInstancehoặcnew java.io.Filexuất phát từJenkinsfilecủa lập trình viên! -
Như chúng ta đã thiết kế ở Bài 35 (Shared Libraries): Toàn bộ các logic nâng cao cần quyền hệ thống chỉ được viết trong kho Global Trusted Shared Library (
jenkins-monolith-shared-lib.git) mà chỉ có đội ngũ DevOps mới có quyền Merge code. Còn tệpJenkinsfilenằm trong kho code của dự án Monolith chỉ được phép gọi duy nhất hàmmonolithStandardPipeline(...)!
5. Lớp giáp 4: Kích hoạt Content Security Policy (CSP) và Cô lập Agent
-
Bảo vệ trình duyệt khi xem báo cáo HTML (
Content-Security-Policy): Khi Jenkins hiển thị các trang báo cáo HTML (như Coverage Report ở Bài 15 hay kết quả Test), nếu bên trong tên một test case có chèn mã độc<script>...</script>, người xem báo cáo có thể bị tấn công XSS. Jenkins mặc định bật một bộ luật CSP rất chặt. Hãy đảm bảo trongJAVA_OPTScủa bạn không bao giờ xóa trắng thuộc tínhhudson.model.DirectoryBrowserSupport.CSP. -
Kiểm tra
Agent -> Controller Security: Từ phiên bản Jenkins 2.326 trở đi, hệ thốngAgent-to-Controller Access Controlđã được bật cứng mặc định, ngăn chặn tiến trình chạy trên máy chủ Worker Agent (monolith-worker-01) đọc hoặc ghi đè các tệp tin nhạy cảm trong/var/jenkins_homecủa Master. -
Chuyển từ Docker Socket (
/var/run/docker.sock) sangRootless DockerhoặcKanikotrên Agent: Trên máy chủ Worker Agent (Bài 31), để tránh việc container đang build chiếm quyềnrootcủa máy chủ thông qua/var/run/docker.sock, bạn nên cấu hình Docker Rootless Mode (chạy Docker Daemon dưới quyền của chính userjenkinskhông có đặc quyềnroot) hoặc bật tính nănguserns-remaptrong/etc/docker/daemon.json:JSON
{ "userns-remap": "default", "no-new-privileges": true }Cờ
"no-new-privileges": truengăn chặn tuyệt đối mọi tiến trình bên trong container leo thang đặc quyền thông qua các tệp thực thi có gắn cờsetuid!
6. Tự động hóa toàn bộ cấu hình Hardening trong jenkins.yaml (JCasC)
Để đảm bảo mọi thiết lập bảo mật ở trên không bao giờ bị ai đó vô tình tắt đi trên giao diện web, bạn hãy bổ sung khối cấu hình Security Hardening chuẩn dưới đây vào tệp jenkins.yaml (Bài 34):
YAML
jenkins:
# 1. Tắt hoàn toàn cổng TCP Agent (JNLP) vì chúng ta chỉ dùng SSH Agent an toàn (Bài 31)
slaveAgentPort: -1
# 2. Kích hoạt bảo vệ chống giả mạo yêu cầu CSRF (Crumb Issuer)
crumbIssuer:
standard:
excludeClientIPFromCrumb: false
# 3. Chặn các giao thức CLI cũ không an toàn qua Remoting
remotingSecurity:
enabled: true
security:
# 4. Ẩn thông tin phiên bản Jenkins trên Header phản hồi HTTP để chống dò quét lỗ hổng tự động
apiToken:
creationOfLegacyTokenEnabled: false
tokenGenerationOnCreationEnabled: false
usageStatisticsEnabled: true
# 5. Khóa chặt cấu hình Git Host Key Verification tránh tấn công Man-in-the-Middle (MitM)
gitHostKeyVerificationConfiguration:
sshHostKeyVerificationStrategy: "knownHostsFileVerificationStrategy"
All rights reserved