iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
DevOps

DevOps 進化論:從全能型戰士到安全守門員系列 第 29

Day 29|DevSecOps 全貌:把安全融入 CI/CD 的完整流程

  • 分享至 

  • xImage
  •  

● 前言

從 Day 24 到 Day 28,我們陸續談了 SAST、依賴掃描、容器掃描、DAST、Secrets 管理。
今天要把這些安全檢查收斂起來,組合成一條真正的 安全 CI/CD 流程 (Secure Pipeline)。
這樣的設計不只是附加檢查,而是把安全視為軟體交付的基本要件。


● DevSecOps 流程圖

https://ithelp.ithome.com.tw/upload/images/20250911/20178156Wmxi8qFKVm.png

👉 這張圖示意了安全檢查如何融入每個階段:
🔹SAST:提交程式碼時檢查潛在漏洞。
🔹依賴掃描:Build 階段檢查套件是否有已知漏洞。
🔹Trivy:容器 Image 打包後做掃描,避免帶著漏洞上線。
🔹DAST:部署到測試環境後模擬攻擊。
🔹Sealed Secrets:敏感資訊全程安全管理。


● 範例 CI/CD Pipeline

以 GitHub Actions 為例,這裡示範如何把幾個常見安全檢查工具串進 CI:

  container_scan:
    runs-on: ubuntu-latest
    needs: deps
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image (tag = commit sha)
        run: |
          docker build -t myapp:${{ github.sha }} .

      - name: Trivy scan (HIGH/CRITICAL -> fail)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: 'table'
          ignore-unfixed: true
          vuln-type: 'os,library'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

  deploy_test:
    runs-on: ubuntu-latest
    needs: container_scan
    steps:
      - uses: actions/checkout@v4

      - name: Start test stack (Docker Compose)
        env:
          GIT_SHA: ${{ github.sha }}
        run: |
          docker compose -f docker-compose.test.yml up -d
          echo "Waiting for health..."
          # 等到 web 服務健康或逾時
          for i in {1..30}; do
            if [ "$(docker inspect -f '{{json .State.Health.Status}}' web 2>/dev/null)" = "\"healthy\"" ]; then
              echo "Service is healthy."
              break
            fi
            sleep 5
          done
          docker ps
          docker compose -f docker-compose.test.yml ps

      - name: Show logs (for debugging if needed)
        if: failure()
        run: |
          docker compose -f docker-compose.test.yml logs --no-color > compose-logs.txt || true
        shell: bash

      - name: Upload compose logs
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: compose-logs
          path: compose-logs.txt
          if-no-files-found: ignore

  dast:
    runs-on: ubuntu-latest
    needs: deploy_test
    steps:
      - name: Run ZAP Baseline in same docker network
        run: |
          # 讓 ZAP 容器加入 compose 網路,直接掃服務主機名 "web"
          docker run --rm -t \
            --network devsecops-testnet \
            owasp/zap2docker-stable \
            zap-baseline.py -t http://web:8000/ -m 5 \
              -J zap-report.json -r zap-report.html -a

      - name: Upload ZAP reports
        uses: actions/upload-artifact@v4
        with:
          name: zap-report
          path: |
            zap-report.json
            zap-report.html

      - name: Teardown test stack
        if: always()
        run: |
          docker compose -f docker-compose.test.yml down -v

👉 這條 Pipeline 的重點:

🔹SAST 在 Commit / PR 階段就能回饋給開發者。
🔹依賴掃描 + 容器掃描 保證交付物(Artifact)沒有已知漏洞。
🔹DAST 在測試環境做黑箱測試,檢查實際運作風險。
🔹Sealed Secrets 則確保敏感資訊不會明文出現在 Repo。

🗂️專案目錄:

https://ithelp.ithome.com.tw/upload/images/20250911/201781562lJrNoiwzF.png

▪實作步驟:

1.專案準備(對應 jobs: sast、deps)

1️⃣ app/main.py
一個最簡單的 FastAPI API,方便被掃描與部署。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello DevSecOps"}

2️⃣ requirements.txt
最小依賴清單,讓 pip-audit 可以掃描套件漏洞。

fastapi==0.111.0
uvicorn==0.30.0

3️⃣ Dockerfile
一個基礎的容器化檔案,讓 CI 可以 build Image 後交給 Trivy 掃描。

# 使用 Python 官方基底映像
FROM python:3.12-slim

# 設定工作目錄
WORKDIR /app

# 複製 requirements 並安裝
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 複製應用程式
COPY app/ ./app

# 預設執行 Uvicorn
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

👉首先,我們要讓專案具備最基本的掃描條件。建立一個專案資料夾(例如 devsecops-demo-app/),裡面放好應用程式原始碼(例如 app/main.py)、依賴清單 requirements.txt 以及 Dockerfile。接著在 .github/workflows/ 目錄下新增一個 secure-ci.yml,貼上我們的 CI/CD Pipeline 配置。這樣當開發者 push 或發 PR 時,GitHub Actions 就會自動啟動 SAST(Bandit) 與 依賴掃描(pip-audit)。Bandit 負責靜態檢查程式碼中的弱點,而 pip-audit 則會針對 requirements.txt 檢查套件是否有已知漏洞。完成這一步後,專案的「基礎安全檢查」就能在程式碼提交時自動進行,並且回饋到 Pull Request 介面。

2.容器掃描與測試部署(對應 jobs: container_scan、deploy_test、dast)

1️⃣容器掃描
(1)準備 Dockerfile
(2)CI 中自動執行 Trivy 掃描
在 GitHub Actions Workflow 中,container_scan job 會:
1.docker build -t myapp:${{ github.sha }} .
2.用 aquasecurity/trivy-action 掃描 Image
▪ 設定 severity: CRITICAL,HIGH
▪ 設定 exit-code: 1(發現漏洞就 fail)
👉 這一步的重點:確保映像檔不帶已知重大漏洞上線。
2️⃣測試部署
加入 Docker Compose 測試部署檔(docker-compose.test.yml):

name: devsecops-testnet  # 固定網路名稱,方便 ZAP 同網路掃描
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: myapp:${GIT_SHA:-local}
    container_name: web
    ports:
      - "8000:8000"         # 如不需對外暴露,可拿掉
    environment:
      UVICORN_HOST: "0.0.0.0"
      UVICORN_PORT: "8000"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/"]
      interval: 10s
      timeout: 3s
      retries: 10
      start_period: 10s

🔔重點
🔹name: devsecops-testnet:讓 Compose 網路名稱固定,ZAP 容器可以用 --network devsecops-testnet 掛進同網路。
🔹image: myapp:${GIT_SHA}:CI 裡用同一個 Tag($GITHUB_SHA)掃過 Trivy 的 Image 來部署。
🔹healthcheck:讓 CI 可以等到服務健康再掃描。
3️⃣動態應用測試
▪ 使用 OWASP ZAP Baseline 掃描,在 CI job 中會自動執行:

docker run --rm -t owasp/zap2docker-stable \
  zap-baseline.py -t http://test-env/ -m 5 \
  -J zap-report.json -r zap-report.html -a

🔹-t 指定測試環境 URL
🔹-m 5 最多掃描 5 分鐘(可調整)
🔹-J 輸出 JSON 報告
🔹-r 輸出 HTML 報告
🔹-a 嚴格模式:發現 Alert 就讓 job fail
👉接下來,我們要確保應用程式在容器化與部署後依然安全。CI 流程會先用 Dockerfile 建立映像檔,然後透過 Trivy 自動掃描,若偵測到 High 或 Critical 等級的漏洞,PR 就會被阻擋,避免有問題的 Image 被推送出去。當容器掃描通過後,Pipeline 會自動把應用程式部署到測試環境(可依你的環境選擇 kubectl、Helm 或 docker compose)。在測試環境跑起來後,再使用 OWASP ZAP Baseline 進行動態應用測試(DAST),模擬實際攻擊行為,檢查是否有 SQL Injection、XSS 等風險。最後會產出 HTML/JSON 報告,讓團隊可以檢視並修正。這個階段確保應用程式不是只在程式碼層安全,而是整個「打包 → 部署 → 運行」的鏈條都經過檢驗。

3.機敏資訊與 PR Gate(對應 jobs: secrets_apply、Branch Protection)

A. 機敏資訊保護(Sealed Secrets → secrets_apply job)
1️⃣建立原始 Secret(本地執行,不要上傳 Git)

kubectl create secret generic db-secret \
  --from-literal=DB_USER=admin \
  --from-literal=DB_PASS=supersecret \
  -o yaml > db-secret.yaml

2️⃣使用 kubeseal 將 Secret 加密
kubeseal < db-secret.yaml > k8s/secrets/sealed-secret.yaml
3️⃣在 GitHub Actions 中套用 Sealed Secrets
Workflow 會在 main 分支合併後,自動執行:
kubectl apply -f k8s/secrets/sealed-secret.yaml
✅確保部署時會還原成真實 Secret。

B. 設定 PR Gate(Branch Protection)
1️⃣到 GitHub Repo → Settings → Branches → Add Rule。
2️⃣指定分支(例如 main),勾選:
✔️ Require a pull request before merging
✔️ Require status checks to pass before merging
3️⃣在 status checks 清單中,至少勾選:
✔️sast (Bandit)
✔️deps (pip-audit)
✔️container_scan (Trivy)
(可選:dast)
4️⃣測試:建立一個 PR,刻意引入漏洞(例如在 requirements.txt 加一個有漏洞的套件),確認 CI fail 後 PR 無法合併。

👉除了程式碼與容器的安全,機敏資訊也是重要環節。這裡我們使用 Sealed Secrets,先將敏感資訊(例如 DB 密碼、API Key)加密後存放在 k8s/secrets/sealed-secret.yaml,並在 CI 中自動套用。這樣就算 Repo 被公開,敏感資訊也不會以明文外洩。

同時,我們要設定 PR Gate,也就是在 GitHub 的 Branch Protection 規則裡要求 PR 必須通過特定安全檢查(例如 SAST、依賴掃描、容器掃描)才能合併。如果 Bandit 掃到高風險問題,或 Trivy 偵測到嚴重漏洞,PR 就會直接被擋下。這樣能確保任何進到主分支的程式碼與 Image,在安全上都經過把關。

4. 執行、檢視與調整門檻(對應 jobs: 全部)

A. 執行與檢視
1️⃣ 建立/更新 PR → 到 PR → Checks 看各 job 狀態與 Logs。
2️⃣ 於 Artifacts 下載報告:bandit-report.json、pip-audit.sarif、zap-report.html/json、compose-logs.txt。
3️⃣ 到 Repo → Security → Code scanning alerts 集中看 pip-audit.sarif 告警(CVE、修補建議)。
4️⃣ 修正後在 PR 右上角 Re-run jobs 或推新 commit 重新驗證。

B. Gate 調整(依團隊嚴格度)
1️⃣ Bandit(SAST):只擋 HIGH(預設)/ 連 MEDIUM 也擋

# sast job:HIGH+MEDIUM 一起擋(要寬鬆就只留 'HIGH')
python - << 'PY'
import json, sys
r = json.load(open('bandit-report.json'))
bad = [i for i in r.get('results', []) if i.get('issue_severity') in ('HIGH','MEDIUM')]
sys.exit(1 if bad else 0)
PY

2️⃣ Trivy(Image):擋哪些等級

# 寬鬆:只擋 CRITICAL
severity: 'CRITICAL'
exit-code: '1'

# 嚴格:連 MEDIUM 也擋
severity: 'CRITICAL,HIGH,MEDIUM'
exit-code: '1'

3️⃣ ZAP(DAST):是否擋 PR

# 產報告、不擋(去掉 -a)
zap-baseline.py -t http://web:8000/ -m 10 -J zap.json -r zap.html
# 嚴格 Gate(有 Alert 就 fail)
zap-baseline.py -t http://web:8000/ -m 10 -J zap.json -r zap.html -a

4️⃣ pip-audit(依賴):只上報 or 當 Gate

# 只上傳 SARIF(不上 Gate) → 保持你目前做法
# 要當 Gate(找到漏洞就 fail)
pip-audit -r requirements.txt

C. 讓 Gate 真的「擋合併」
1️⃣ Repo → Settings → Branches → Add rule(main)
2️⃣ 勾:✔ Require a pull request before merging、✔ Require status checks to pass before merging
3️⃣ 指定必過 checks:sast、container_scan(選用:deps、dast 看你 Gate 設定)

👉最後,當這條 Pipeline 跑起來後,我們需要學會如何檢視與調整門檻。執行一個 PR 後,可以在 PR 的 Checks 頁面看到每個 Job 是否通過,也可以在 Artifacts 下載報告(如 bandit-report.json、zap-report.html),或者到 Security → Code scanning alerts 集中檢視 pip-audit 的掃描結果。如果發現偵測太嚴格導致 PR 經常被擋,可以調整 YAML:例如只讓 Bandit 在偵測到 High 風險時失敗,Medium 風險則給警告;Trivy 只針對 Critical 與 High 報錯,其他則允許合併。反之,如果團隊想要更嚴格,也可以把 Medium 級別納入 Gate。透過這些設定,我們能讓安全檢查真正符合團隊需求,而不是流於形式。

● 總結

DevSecOps 的精髓在於:
▪ 安全不是最後才加上的檢查,而是貫穿 CI/CD 的每一環。
▪ 從程式碼提交、建構、測試到部署,安全機制都應自動化執行。
▪ 工具可以更換,但思維是一致的:「安全左移」+「自動化」。

👉 下一篇 Day 30|最終回顧 × DevSecOps 展望


上一篇
Day 28|Secrets 管理進階:從 Kubernetes Secret 到加密存放
下一篇
Day 30|最終回顧 × DevSecOps 展望
系列文
DevOps 進化論:從全能型戰士到安全守門員30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言