DevOps Pipeline Integration
DevOps Pipeline Integration
Updated on 03 Jun 2025

Guideline: Tích hợp Scanning API của FSP Core Service vào CI/CD Pipelines

Hướng dẫn này sẽ hướng dẫn bạn cách tích hợp các endpoint quét dịch vụ lõi FSP vào quy trình CI/CD của mình, cho phép tự động quét mã nguồn, quét bí mật và quét bảo mật hình ảnh container trên mỗi lần thay đổi.

1. Tổng quan

Tự động hóa các bước quét bảo mật trong pipeline CI/CD giúp phát hiện lỗ hổng sớm. Hướng dẫn này bao gồm:

  • Yêu cầu chuẩn bị
  • Cách thiết lập xác thực
  • Các bước tích hợp pipeline cho scan code, scan secret, và scan image
  • Cấu hình mẫu (GitLab CI, Jenkins)

2. Yêu cầu chuẩn bị

  • Tài khoản FSP Core Service đang hoạt động, với Orgid và access_key hợp lệ.
  • Truy cập được các endpoint API (ví dụ: /integration/scan-code, /integration/scan-secret, /integration/scan-image và các endpoint lấy kết quả tương ứng).
  • Runner hoặc agent CI/CD có thể kết nối mạng tới API FSP Core Service.
  • Môi trường shell Unix-like (bash, sh) để viết script gọi HTTP.

3. Đặc tả API

3.1. Tích hợp scan code

POST /api/v1/xplat/fsp-core-service/integration/scan-code

Khởi tạo quét mã nguồn cho một kho lưu trữ và commit cụ thể thuộc nhóm FSEC.

Yêu cầu

Headers

Tên Loại Bắt buộc Mô tả
Orgid string Không ID tổ chức để xác thực

Body

Content-Type: application/json

Tên Loại Bắt buộc Mô tả
team_code string Mã của nhóm FSEC
git_repo_url string URL kho lưu trữ Git (ví dụ: GitHub hoặc GitLab)
access_key string Khóa truy cập do FSEC cấp để truy cập kho lưu trữ
branch string Tên nhánh cần quét
commit string SHA của commit trên nhánh cần quét

Ví dụ

POST /api/v1/xplat/fsp-core-service/integration/scan-code HTTP/1.1
Host: api.yourdomain.com
Orgid: 123e4567-e89b-12d3-a456-426614174000
Accept: application/json
Content-Type: application/json

{
  "team_code": "FSEC_TEAM_001",
  "git_repo_url": "https://github.com/example/repo.git",
  "access_key": "abcd1234",
  "branch": "main",
  "commit": "a1b2c3d4e5f6g7h8i9j0"
}

Phản hồi

  • 200 - Successful Response
{
  "data": {
    "errorCode": "F-000",
    "errorMessage": "",
    "data": {
      "request_code": "",
      "request_time": "2025-03-25 13:34:11"
    }
  }
}
  • 422 - Validation Error
{
  "detail": [
    {
      "loc": ["string", 0],
      "msg": "string",
      "type": "string"
    }
  ]
}

3.2. Tích hợp scan secret

POST /api/v1/xplat/fsp-core-service/integration/scan-secret Khởi tạo quét secret cho một kho lưu trữ và commit cụ thể thuộc nhóm FSEC.

Yêu cầu

Headers

Tên Loại Bắt buộc Mô tả
Orgid string Không ID tổ chức để xác thực

Body

Content-Type: application/json

Tên Loại Bắt buộc Mô tả
team_code string Mã của nhóm FSEC
git_repo_url string URL kho lưu trữ Git (ví dụ: GitHub hoặc GitLab)
access_key string Khóa truy cập do FSEC cấp để truy cập kho lưu trữ
branch string Tên nhánh cần quét
commit string SHA của commit trên nhánh cần quét

Ví dụ

POST /api/v1/xplat/fsp-core-service/integration/scan-secret HTTP/1.1
Host: api.yourdomain.com
Orgid: 123e4567-e89b-12d3-a456-426614174000
Accept: application/json
Content-Type: application/json

{
  "team_code": "FSEC_TEAM_001",
  "git_repo_url": "https://github.com/example/repo.git",
  "access_key": "abcd1234",
  "branch": "main",
  "commit": "a1b2c3d4e5f6g7h8i9j0"
}

Phản hồi

  • 200 - Successful Response
{
  "data": {
    "errorCode": "F-000",
    "errorMessage": "",
    "data": {
      "request_code": "",
      "request_time": "2025-03-25 13:34:11"
    }
  }
}
  • 422 - Validation Error
{
  "detail": [
    {
      "loc": ["string", 0],
      "msg": "string",
      "type": "string"
    }
  ]
}

3.3. Tích hợp scan image

POST /api/v1/xplat/fsp-core-service/integration/scan-image Khởi tạo quét image cho một kho lưu trữ và commit cụ thể thuộc nhóm FSEC.

Yêu cầu

Headers

Tên Loại Bắt buộc Mô tả
Orgid string Không ID tổ chức để xác thực

Body

Content-Type: application/json

Tên Loại Bắt buộc Mô tả
team_code string Mã của nhóm FSEC
access_key string Khóa truy cập do FSEC cấp cho registry image
image_url string Đường dẫn registry và tag của image (ví dụ: repo:tag)

Ví dụ

POST /api/v1/xplat/fsp-core-service/integration/scan-image HTTP/1.1
Host: api.yourdomain.com
Orgid: 123e4567-e89b-12d3-a456-426614174000
Accept: application/json
Content-Type: application/json

{
  "team_code": "FSEC_TEAM_001",
  "access_key": "abcd1234",
  "image_url": "registry.example.com/myapp:latest"
}

Phản hồi

  • 200 - Successful Response
{
  "data": {
    "errorCode": "F-000",
    "errorMessage": "",
    "data": {
      "request_code": "",
      "request_time": "2025-03-25 13:34:11"
    }
  }
}
  • 422 - Validation Error
{
  "detail": [
    {
      "loc": ["string", 0],
      "msg": "string",
      "type": "string"
    }
  ]
}

3.4. Tích hợp lấy kết quả scan code

POST /api/v1/xplat/fsp-core-service/integration/get-scan-code-result Lấy kết quả của một lần quét mã nguồn đã khởi tạo trước đó bằng request_code.

Yêu cầu

Headers

Tên Loại Bắt buộc Mô tả
Orgid string Không ID tổ chức để xác thực

Body

Content-Type: application/json

Tên Loại Bắt buộc Mô tả
request_code string Mã yêu cầu trả về từ /scan-code
access_key string Khóa truy cập do FSEC cấp

Ví dụ

POST /api/v1/xplat/fsp-core-service/integration/get-scan-code-result HTTP/1.1
Host: api.yourdomain.com
Orgid: 123e4567-e89b-12d3-a456-426614174000
Accept: application/json
Content-Type: application/json

{
  "request_code": "RSC-123456",
  "access_key": "abcd1234"
}

Phản hồi

  • 200 - Successful Response
{
  "data": {
    "errorCode": "F-000",
    "errorMessage": "",
    "data": {
      "request_code": "RSC-123456",
      "scan_status": "COMPLETED",
      "scan_result": {
        "L": 0,
        "C": 0,
        "M": 4,
        "H": 0
      },
      "finish_time": "2025-03-25 13:34:11",
      "quality_gate": "OK"
    }
  }
}
  • 422 - Validation Error
{
  "detail": [
    {
      "loc": ["string", 0],
      "msg": "string",
      "type": "string"
    }
  ]
}

3.5. Tích hợp lấy kết quả scan secret

POST /api/v1/xplat/fsp-core-service/integration/get-scan-secret-result Lấy kết quả của một lần quét secret đã khởi tạo trước đó bằng request_code.

Yêu cầu

Headers

Tên Loại Bắt buộc Mô tả
Orgid string Không ID tổ chức để xác thực

Body

Content-Type: application/json

Tên Loại Bắt buộc Mô tả
request_code string Mã yêu cầu trả về từ /scan-secret
access_key string Khóa truy cập do FSEC cấp

Ví dụ

POST /api/v1/xplat/fsp-core-service/integration/get-scan-secret-result HTTP/1.1
Host: api.yourdomain.com
Orgid: 123e4567-e89b-12d3-a456-426614174000
Accept: application/json
Content-Type: application/json

{
  "request_code": "RSC-123456",
  "access_key": "abcd1234"
}

Phản hồi

  • 200 - Successful Response
{
  "data": {
    "errorCode": "F-000",
    "errorMessage": "",
    "data": {
      "request_code": "RSC-123456",
      "scan_status": "COMPLETED",
      "scan_result": {
        "L": 0,
        "C": 0,
        "M": 4,
        "H": 0
      },
      "finish_time": "2025-03-25 13:34:11",
      "quality_gate": "OK"
    }
  }
}
  • 422 - Validation Error
{
  "detail": [
    {
      "loc": ["string", 0],
      "msg": "string",
      "type": "string"
    }
  ]
}

3.6. Tích hợp lấy kết quả scan image

POST /api/v1/xplat/fsp-core-service/integration/get-scan-image-result Lấy kết quả của một lần quét image đã khởi tạo trước đó bằng request_code.

Yêu cầu

Headers

Tên Loại Bắt buộc Mô tả
Orgid string Không ID tổ chức để xác thực

Body

Content-Type: application/json

Tên Loại Bắt buộc Mô tả
request_code string Mã yêu cầu trả về từ /scan-image
access_key string Khóa truy cập do FSEC cấp

Ví dụ

POST /api/v1/xplat/fsp-core-service/integration/get-scan-image-result HTTP/1.1
Host: api.yourdomain.com
Orgid: 123e4567-e89b-12d3-a456-426614174000
Accept: application/json
Content-Type: application/json

{
  "request_code": "RSC-123456",
  "access_key": "abcd1234"
}

Phản hồi

  • 200 - Successful Response
{
  "data": {
    "errorCode": "F-000",
    "errorMessage": "",
    "data": {
      "request_code": "RSC-123456",
      "scan_status": "COMPLETED",
      "scan_result": {
        "L": 0,
        "C": 0,
        "M": 4,
        "H": 0
      },
      "finish_time": "2025-03-25 13:34:11",
      "quality_gate": "OK"
    }
  }
}
  • 422 - Validation Error
{
  "detail": [
    {
      "loc": ["string", 0],
      "msg": "string",
      "type": "string"
    }
  ]
}

4. Pipeline flow

4.1. Giai đoạn scan code (sau bước checkout source)

  • Kích hoạt scan: Gọi POST /api/v1/xplat/fsp-core-service/integration/scan-code với thông tin chi tiết về repository.
  • Lấy kết quả: Gọi POST /api/v1/xplat/fsp-core-service/integration/get-scan-code-result và lặp lại đến khi status = SUCCEEDED.
  • Đánh giá pass/fail: Dựa trên trường issues_found hoặc các ngưỡng chất lượng (quality gate) riêng.

4.2. Giai đoạn scan secret (sau bước checkout source)

  • Kích hoạt scan: Gọi POST /api/v1/xplat/fsp-core-service/integration/scan-secret với payload giống bước scan code.
  • Lấy kết quả: Gọi POST /api/v1/xplat/fsp-core-service/integration/get-scan-secret-result và lặp lại đến khi status = SUCCEEDED.
  • Đánh giá pass/fail: Dựa trên ngưỡng issues_found.

4.3. Giai đoạn scan image (sau bước build)

  • Kích hoạt scan: Gọi POST /api/v1/xplat/fsp-core-service/integration/scan-image với tag của image.
  • Lấy kết quả: Gọi POST /api/v1/xplat/fsp-core-service/integration/get-scan-secret-result và lặp lại đến khi status = SUCCEEDED.
  • Đánh giá pass/fail: Dựa trên kết quả scan_result (L/M/H/C) và ngưỡng chất lượng (quality gate) riêng.

5. Cấu hình mẫu

5.1. GitLab CI

variables:
  ORGID: "$FSP_ORGID"
  ACCESS_KEY: "$FSP_ACCESS_KEY"
  API_URL: "https://api.yourdomain.com/api/v1/xplat/fsp-core-service"
  TEAM_CODE: "FSEC_TEAM_001"

stages:
  - code-scan
  - secret-scan
  - build
  - image-scan

# Shared script for code & secret scans
.scan-stage: &scan-stage
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq
  script:
    - echo "Trigger scan"
    - RESPONSE=$(curl -s -X POST "$API_URL/integration/${ENDPOINT}" \
        -H "Orgid: $ORGID" \
        -H "access_key: $ACCESS_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"team_code\":\"$TEAM_CODE\",\"git_repo_url\":\"$CI_PROJECT_URL.git\",\"branch\":\"$CI_COMMIT_REF_NAME\",\"commit\":\"$CI_COMMIT_SHA\"}")
    - CODE=$(echo "$RESPONSE" | jq -r '.data.data.request_code')
    - |
      echo "Polling for scan result..."
      until [ "$(curl -s -X POST "$API_URL/integration/$RESULT_ENDPOINT" \
            -H "Orgid: $ORGID" -H "access_key: $ACCESS_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"request_code\":\"$CODE\",\"access_key\":\"$ACCESS_KEY\"}" \
        | jq -r '.data.data.status')" = "COMPLETED" ]; do
        sleep 10
      done
    - ISSUES=$(curl -s -X POST "$API_URL/integration/$RESULT_ENDPOINT" \
        -H "Orgid: $ORGID" -H "access_key: $ACCESS_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"request_code\":\"$CODE\",\"access_key\":\"$ACCESS_KEY\"}" \
        | jq '.data.data.issues_found')
    - if [ "$ISSUES" -gt 0 ]; then echo "Found $ISSUES issues in $ENDPOINT"; exit 1; fi

code-scan:
  stage: code-scan
  variables:
    ENDPOINT: "scan-code"
    RESULT_ENDPOINT: "get-scan-code-result"
  <<: *scan-stage

secret-scan:
  stage: secret-scan
  variables:
    ENDPOINT: "scan-secret"
    RESULT_ENDPOINT: "get-scan-secret-result"
  <<: *scan-stage

build:
  stage: build
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .

image-scan:
  stage: image-scan
  script:
    - echo "Trigger image scan"
    - RESPONSE=$(curl -s -X POST "$API_URL/integration/scan-image" \
        -H "Orgid: $ORGID" \
        -H "access_key: $ACCESS_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"team_code\":\"$TEAM_CODE\",\"access_key\":\"$ACCESS_KEY\",\"image_url\":\"registry.example.com/myapp:$CI_COMMIT_SHA\"}")
    - CODE=$(echo "$RESPONSE" | jq -r '.data.data.request_code')
    - |
      echo "Polling for image scan result..."
      until [ "$(curl -s -X POST "$API_URL/integration/get-scan-image-result" \
            -H "Orgid: $ORGID" -H "access_key: $ACCESS_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"request_code\":\"$CODE\",\"access_key\":\"$ACCESS_KEY\"}" \
        | jq -r '.data.data.scan_status')" = "COMPLETED" ]; do
        sleep 10
      done
    - HIGH=$(curl -s -X POST "$API_URL/integration/get-scan-image-result" \
        -H "Orgid: $ORGID" -H "access_key: $ACCESS_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"request_code\":\"$CODE\",\"access_key\":\"$ACCESS_KEY\"}" \
        | jq '.data.data.scan_result.H')
    - if [ "$HIGH" -gt 0 ]; then echo "High severity issues found"; exit 1; fi

5.2. Jenkins Pipeline

pipeline {
  agent any
  environment {
    ORGID = credentials('FSP_ORGID')
    ACCESS_KEY = credentials('FSP_ACCESS_KEY')
    API_URL = 'https://api.yourdomain.com/api/v1/xplat/fsp-core-service'
  }
  stages {
    stage('Code Scan') {
      steps {
        script {
          def payload = [team_code: 'FSEC_TEAM_001', git_repo_url: env.GIT_URL, branch: env.BRANCH_NAME, commit: env.GIT_COMMIT]
          def response = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_JSON', httpMode: 'POST', requestBody: groovy.json.JsonOutput.toJson(payload), url: "${API_URL}/integration/scan-code", customHeaders: [[name:'Orgid', value:ORGID], [name:'access_key', value:ACCESS_KEY]]
          def code = readJSON(text: response.content).data.data.request_code
          timeout(time: 5, unit: 'MINUTES') {
            waitUntil {
              def result = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_JSON', httpMode: 'POST', requestBody: groovy.json.JsonOutput.toJson([request_code: code, access_key: ACCESS_KEY]), url: "${API_URL}/integration/get-scan-code-result", customHeaders: [[name:'Orgid', value:ORGID], [name:'access_key', value:ACCESS_KEY]]
              return readJSON(text: result.content).data.data.status == 'COMPLETED'
            }
          }
          def issues = readJSON(text: result.content).data.data.issues_found
          if (issues > 0) { error "Found ${issues} code issues" }
        }
      }
    }
    stage('Secret Scan') {
      steps {
        script {
          def payload = [team_code: 'FSEC_TEAM_001', git_repo_url: env.GIT_URL, branch: env.BRANCH_NAME, commit: env.GIT_COMMIT]
          def response = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_JSON', httpMode: 'POST', requestBody: groovy.json.JsonOutput.toJson(payload), url: "${API_URL}/integration/scan-secret", custom