[SecDevOps] Biến “Phân tích mã tĩnh + Kiểm thử đơn vị” thành cổng chặn rủi ro ngay trong CI
Bạn có thể ship code nhanh, nhưng đừng để bảo mật và chất lượng “lọt lưới”. Bài này mình gói gọn cách đưa Bandit (phân tích mã tĩnh bảo mật cho Python) và pytest/unittest vào verify stage của pipeline để mọi commit đều được quét lỗ hổng và chạy test—thất bại là chặn merge.
Vì sao phải chạy ở verify stage?
Bandit – ảnh chụp nhanh bảo mật mã nguồn Python
Bạn báo cáo hiện tại:
pip install bandit # Quét toàn bộ repo (đúng cú pháp là . chứ không phải ..) bandit -r . # Quét 1 file bandit app/foo.py # Tăng chi tiết bandit -r . -v Tích hợp cấu hình dự án
Tạo .bandit ở thư mục gốc repo để tiêu chuẩn hóa cho cả team:
# .bandit [bandit] targets: . exclude: tests,venv,.venv,build,dist skips: B101,B404 # ví dụ: tùy lý do, cân nhắc kỹ khi skip severity-level: LOW confidence-level: MEDIUM recursive: True Chiến lược “gating” khuyến nghị
Nếu buộc phải chấp nhận rủi ro có kiểm soát, chú thích rõ lý do:
subprocess.Popen(user_input, shell=True) # nosec: đã sandbox, input sanitized bằng <cơ chế>, kèm link threat model
Reviewer phải kiểm kỹ và link về ticket/thiết kế. Pre-commit hook (chạy trước khi commit)
# .pre-commit-config.yaml repos: - repo: https://github.com/PyCQA/bandit rev: 1.7.9 hooks: - id: bandit args: ["-r", ".", "--severity-level", "medium", "--confidence-level", "medium"] pip install pre-commit pre-commit install
Kiểm thử đơn vị – “hợp đồng hành vi” cho code
pip install --upgrade pytest pytest -vv # lọc 1 test pytest -k test_get_device -vv # dừng ở lỗi đầu tiên pytest -x Chuẩn hóa cấu hình pytest
# pytest.ini [pytest] addopts = -vv --maxfail=1 testpaths = tests python_files = test_*.py *_test.py Khi test fail như log của bạn
tests/test_routes.py .F. FAIL TestNetInventoryAPI.test_get_device ... AssertionError at line 73
Checklist xử lý nhanh, không đoán mò:
Gắn vào GitLab CI (verify stage)
stages: - verify - build - deploy variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" .bandit_template: &bandit image: python:3.11-slim stage: verify before_script: - python -V - pip install --upgrade pip - pip install bandit allow_failure: false .pytest_template: &pytest image: python:3.11-slim stage: verify before_script: - python -V - pip install --upgrade pip - pip install -r requirements.txt || true - pip install pytest artifacts: when: always reports: junit: report.xml # để hiển thị trong UI GitLab paths: - report.xml allow_failure: false bandit_scan: <<: *bandit script: - bandit -r . --severity-level medium --confidence-level medium -f txt -o bandit.txt artifacts: when: always paths: - bandit.txt unit_tests: <<: *pytest script: - pytest -vv --junitxml=report.xml
Mẹo siết chặt dần
Quy ước nhóm – để pipeline nói thay team
Lộ trình nâng cấp
Kết
Với Bandit và pytest/unittest cắm ở verify stage, bạn có “cánh cổng” tự động chặn rủi ro bảo mật và lỗi logic trước khi code lan sang môi trường sau. Báo cáo của bạn đã khá sạch (chỉ 2 Low). Hãy:
Bạn có thể ship code nhanh, nhưng đừng để bảo mật và chất lượng “lọt lưới”. Bài này mình gói gọn cách đưa Bandit (phân tích mã tĩnh bảo mật cho Python) và pytest/unittest vào verify stage của pipeline để mọi commit đều được quét lỗ hổng và chạy test—thất bại là chặn merge.
Vì sao phải chạy ở verify stage?
- Phát hiện sớm: Bandit tìm mẫu code nguy cơ (hardcoded secrets, subprocess nguy hiểm, dùng crypto yếu, eval/exec, path traversal…).
- Fail-fast: Một lỗi bảo mật hoặc test fail → job trả mã thoát ≠ 0 → pipeline dừng, không phát tán lỗi sang các stage sau.
- Chuẩn hóa: Bộ quy tắc + ngưỡng rõ ràng giúp team tranh luận ít đi, fix tập trung.
Bandit – ảnh chụp nhanh bảo mật mã nguồn Python
Bạn báo cáo hiện tại:
- Mã quét: 201 dòng, #nosec bị bỏ qua: 0
- Kết quả theo mức độ nghiêm trọng: Thấp: 2, Trung bình/Cao: 0
- Theo độ tin cậy: Trung bình: 2
Diễn giải nhanh: có 2 phát hiện mức độ nghiêm trọng Thấp và độ tin cậy Trung bình. Không có Medium/High severity. Vẫn nên xem xét và/hoặc triage.
Cài và chạypip install bandit # Quét toàn bộ repo (đúng cú pháp là . chứ không phải ..) bandit -r . # Quét 1 file bandit app/foo.py # Tăng chi tiết bandit -r . -v Tích hợp cấu hình dự án
Tạo .bandit ở thư mục gốc repo để tiêu chuẩn hóa cho cả team:
# .bandit [bandit] targets: . exclude: tests,venv,.venv,build,dist skips: B101,B404 # ví dụ: tùy lý do, cân nhắc kỹ khi skip severity-level: LOW confidence-level: MEDIUM recursive: True Chiến lược “gating” khuyến nghị
- Giai đoạn 1 (áp dụng ngay): Fail nếu có Medium/Critical severity hoặc High confidence.
bandit -r . --severity-level medium --confidence-level medium - Giai đoạn 2 (siết dần): Nâng ngưỡng lên fail cả Low severity nếu số lượng vượt ngưỡng cho phép (ví dụ >0 ở đường code nhạy cảm, hoặc >N toàn repo).
- Baseline (nếu code cũ có nợ kỹ thuật):
bandit -r . -f json -o bandit-baseline.json bandit -r . --baseline bandit-baseline.json
Cách này cho phép block chỉ các phát hiện mới.
Nếu buộc phải chấp nhận rủi ro có kiểm soát, chú thích rõ lý do:
subprocess.Popen(user_input, shell=True) # nosec: đã sandbox, input sanitized bằng <cơ chế>, kèm link threat model
Reviewer phải kiểm kỹ và link về ticket/thiết kế. Pre-commit hook (chạy trước khi commit)
# .pre-commit-config.yaml repos: - repo: https://github.com/PyCQA/bandit rev: 1.7.9 hooks: - id: bandit args: ["-r", ".", "--severity-level", "medium", "--confidence-level", "medium"] pip install pre-commit pre-commit install
Kiểm thử đơn vị – “hợp đồng hành vi” cho code
- unittest: built-in, tốt cho cấu trúc class-based.
- pytest: gọn, giàu plugin, auto-discovery test_*.py hoặc *_test.py.
pip install --upgrade pytest pytest -vv # lọc 1 test pytest -k test_get_device -vv # dừng ở lỗi đầu tiên pytest -x Chuẩn hóa cấu hình pytest
# pytest.ini [pytest] addopts = -vv --maxfail=1 testpaths = tests python_files = test_*.py *_test.py Khi test fail như log của bạn
tests/test_routes.py .F. FAIL TestNetInventoryAPI.test_get_device ... AssertionError at line 73
Checklist xử lý nhanh, không đoán mò:
- Reproduce cục bộ: pytest -k test_get_device -vv.
- Đọc fixture/data contract: API trả đúng HTTP code/body schema chưa?
- Tách lỗi logic vs. dữ liệu:
- Logic: sai điều kiện, nhầm route, thiếu mock.
- Dữ liệu: test fixture lệch, seed DB khác bản docs.
- Ghi thêm assert/print có mục tiêu hoặc dùng pytest --maxfail=1 -vv để khoanh vùng.
- Nếu là breaking change hợp lệ: cập nhật test và CHANGELOG.
Gắn vào GitLab CI (verify stage)
stages: - verify - build - deploy variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" .bandit_template: &bandit image: python:3.11-slim stage: verify before_script: - python -V - pip install --upgrade pip - pip install bandit allow_failure: false .pytest_template: &pytest image: python:3.11-slim stage: verify before_script: - python -V - pip install --upgrade pip - pip install -r requirements.txt || true - pip install pytest artifacts: when: always reports: junit: report.xml # để hiển thị trong UI GitLab paths: - report.xml allow_failure: false bandit_scan: <<: *bandit script: - bandit -r . --severity-level medium --confidence-level medium -f txt -o bandit.txt artifacts: when: always paths: - bandit.txt unit_tests: <<: *pytest script: - pytest -vv --junitxml=report.xml
Mẹo siết chặt dần
- Bật MR widget cho JUnit report để dev thấy lỗi ngay trong Merge Request.
- Thiết lập branch protection: chỉ merge khi verify stage pass.
- Nếu repo lớn, thêm caching hoặc tách matrix job (ví dụ test nhanh vs. test tích hợp có DB/migration).
Quy ước nhóm – để pipeline nói thay team
- Severity/Confidence policy: chấp nhận tối đa mức nào ở môi trường dev/staging/prod.
- Tài liệu hóa false positive: bắt buộc lý do và ticket link cho mỗi #nosec.
- Coverage target: ví dụ ≥ 70% giờ, nâng dần theo quý.
- Break-the-build: test hoặc security fail đều block merge—không exceptions trừ khi có waiver đã duyệt.
Lộ trình nâng cấp
- Thêm mã hóa bí mật: trộn detect-secrets/trufflehog trong pre-commit.
- Thêm type checking: mypy để giảm lỗi runtime.
- Bổ sung SCA (Software Composition Analysis): pip-audit/pipenv check để quét CVE trong dependency.
- Báo cáo tập trung: publish artifacts Bandit + JUnit, bật badge trạng thái trên README.
Kết
Với Bandit và pytest/unittest cắm ở verify stage, bạn có “cánh cổng” tự động chặn rủi ro bảo mật và lỗi logic trước khi code lan sang môi trường sau. Báo cáo của bạn đã khá sạch (chỉ 2 Low). Hãy:
- Triage 2 phát hiện Low, quyết định fix hay chấp nhận có kiểm soát.
- Sửa test fail tại test_get_device, đảm bảo contract API rõ và test data khớp.
- Đưa cấu hình .bandit, pytest.ini và GitLab CI ở trên vào repo để chuẩn hóa cho cả team.