0

🚀 Chapter 4 — Production: Phần E — Security Gate với Amazon ECR

Chapter 4D, chúng ta đã hoàn thành:

GitHub
   ↓
GitHub Actions
   ↓
Reusable Workflow
   ↓
Docker Build
   ↓
Git SHA Tag
   ↓
Amazon ECR
   ↓
Immutable Image Tag

Tuy nhiên, image đã được build và push lên ECR chưa có nghĩa là image đó an toàn để deploy.

Trong phần này, chúng ta sẽ bổ sung Security Gate:

Docker Build
    ↓
Push Image lên ECR
    ↓
Vulnerability Scan
    ↓
Security Gate
    ├── FAIL → Dừng pipeline
    └── PASS → Cho phép deploy

Amazon ECR hỗ trợ quét vulnerability cho container image. Basic scanning tập trung vào OS packages, trong khi Enhanced scanning tích hợp với Amazon Inspector và có thể quét cả OS packages và programming-language packages. Trong lab này, chúng ta sử dụng ECR Basic Scanning với Scan on Push để dễ triển khai và tập trung vào logic Security Gate.


1. Mục tiêu

Sau phần này, chúng ta sẽ có:

✅ Image được scan sau khi push lên ECR

✅ GitHub Actions chờ kết quả scan

✅ Pipeline bị fail nếu có vulnerability nghiêm trọng

✅ Pipeline tiếp tục nếu image đạt điều kiện security

✅ Không deploy image chưa được kiểm tra

✅ Có thể áp dụng cho nhiều service của Online Boutique

Kiến trúc mới:

Developer
    ↓
git push
    ↓
GitHub
    ↓
GitHub Actions
    ↓
Reusable Workflow
    ↓
Test
    ↓
Docker Build
    ↓
Push Image lên ECR
    ↓
ECR Vulnerability Scan
    ↓
Security Gate
    ├── FAIL → Stop
    └── PASS → Continue

2. Kiểm tra môi trường

Trên máy local, di chuyển vào project:

cd online-boutique-cicd

Kiểm tra Git:

git status

Kiểm tra workflow:

find .github/workflows -type f

Ví dụ:

.github/workflows/
├── reusable-ci-cd.yml
└── scale-ci-cd.yml

Kiểm tra AWS CLI:

aws --version

Kiểm tra AWS account:

aws sts get-caller-identity

Kiểm tra AWS Region:

aws configure get region

Nếu chưa có region, đặt region Tokyo:

aws configure set region ap-northeast-1

Kiểm tra các ECR repository:

aws ecr describe-repositories \
  --query 'repositories[?starts_with(repositoryName, `online-boutique-`)].repositoryName' \
  --output table

Ví dụ:

online-boutique-frontend
online-boutique-cartservice
online-boutique-paymentservice
online-boutique-checkoutservice

3. Kiểm tra trạng thái scanning của ECR

Kiểm tra repository frontend:

aws ecr describe-repositories \
  --repository-names online-boutique-frontend \
  --query 'repositories[0].imageScanningConfiguration' \
  --output table

Nếu kết quả tương tự:

scanOnPush
----------
False

thì cần bật Scan on Push.


4. Bật Scan on Push cho repository

Chạy lệnh:

aws ecr put-image-scanning-configuration \
  --repository-name online-boutique-frontend \
  --image-scanning-configuration scanOnPush=true

Kiểm tra lại:

aws ecr describe-repositories \
  --repository-names online-boutique-frontend \
  --query 'repositories[0].imageScanningConfiguration' \
  --output table

Kết quả mong đợi:

scanOnPush
----------
True

Scan on Push sẽ tự động kích hoạt quá trình scan khi image được push vào repository.


5. Bật Scan on Push cho các service khác

Chạy lệnh:

for repo in \
  online-boutique-frontend \
  online-boutique-cartservice \
  online-boutique-productcatalogservice \
  online-boutique-checkoutservice \
  online-boutique-paymentservice \
  online-boutique-shippingservice
do
  echo "Enabling scan on push for $repo"

  aws ecr put-image-scanning-configuration \
    --repository-name "$repo" \
    --image-scanning-configuration scanOnPush=true
done

Kiểm tra:

for repo in \
  online-boutique-frontend \
  online-boutique-cartservice \
  online-boutique-productcatalogservice \
  online-boutique-checkoutservice \
  online-boutique-paymentservice \
  online-boutique-shippingservice
do
  echo "===== $repo ====="

  aws ecr describe-repositories \
    --repository-names "$repo" \
    --query 'repositories[0].{Repository:repositoryName,ScanOnPush:imageScanningConfiguration.scanOnPush}' \
    --output table
done

Kết quả mong đợi:

ScanOnPush
----------
True

6. Kiểm tra image đã có trong ECR

Kiểm tra image của frontend:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --query 'sort_by(imageDetails,&imagePushedAt)[-1].{Tags:imageTags,Digest:imageDigest,Pushed:imagePushedAt}' \
  --output table

Nếu repository đã có image, bạn có thể lấy tag:

IMAGE_TAG=$(aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --query 'sort_by(imageDetails,&imagePushedAt)[-1].imageTags[0]' \
  --output text)

Kiểm tra:

echo "$IMAGE_TAG"

Ví dụ:

a81c92f

Nếu repository chưa có image, chúng ta sẽ push image mới thông qua GitHub Actions ở các bước sau.


7. Xem kết quả vulnerability scan bằng AWS CLI

Đặt biến:

REPOSITORY_NAME=online-boutique-frontend
IMAGE_TAG=a81c92f

Thay a81c92f bằng tag thực tế trong ECR.

Lấy kết quả scan:

aws ecr describe-image-scan-findings \
  --repository-name "$REPOSITORY_NAME" \
  --image-id imageTag="$IMAGE_TAG" \
  --query 'imageScanFindings.findingSeverityCounts' \
  --output table

Ví dụ:

CRITICAL    HIGH    MEDIUM    LOW    INFORMATIONAL
--------    ----    ------    ---    -------------
0           1       4        8      2

Kiểm tra trạng thái scan:

aws ecr describe-image-scan-findings \
  --repository-name "$REPOSITORY_NAME" \
  --image-id imageTag="$IMAGE_TAG" \
  --query 'imageScanStatus' \
  --output table

Ví dụ:

status
------
COMPLETE

Một số trạng thái thường gặp:

IN_PROGRESS
COMPLETE
FAILED
UNSUPPORTED

Nếu kết quả là IN_PROGRESS, cần chờ scan hoàn thành trước khi đánh giá Security Gate.


8. Tạo script kiểm tra Security Gate

Tạo thư mục scripts:

mkdir -p scripts

Tạo file:

nano scripts/ecr-security-gate.sh

Thêm nội dung:

#!/usr/bin/env bash

set -euo pipefail

REPOSITORY_NAME="${1:?Repository name is required}"
IMAGE_TAG="${2:?Image tag is required}"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"
MAX_WAIT_SECONDS="${MAX_WAIT_SECONDS:-300}"
POLL_INTERVAL_SECONDS="${POLL_INTERVAL_SECONDS:-10}"

echo "========================================"
echo "ECR Security Gate"
echo "========================================"
echo "Repository: $REPOSITORY_NAME"
echo "Image tag:  $IMAGE_TAG"
echo "Region:     $AWS_REGION"
echo "========================================"

elapsed_seconds=0

while true; do
  scan_status="$(
    aws ecr describe-image-scan-findings \
      --repository-name "$REPOSITORY_NAME" \
      --image-id imageTag="$IMAGE_TAG" \
      --region "$AWS_REGION" \
      --query 'imageScanStatus.status' \
      --output text 2>/dev/null || true
  )"

  echo "Scan status: ${scan_status:-UNKNOWN}"

  if [[ "$scan_status" == "COMPLETE" ]]; then
    break
  fi

  if [[ "$scan_status" == "FAILED" || "$scan_status" == "UNSUPPORTED" ]]; then
    echo "Security Gate: FAIL"
    echo "Image scan did not complete successfully."
    exit 1
  fi

  if (( elapsed_seconds >= MAX_WAIT_SECONDS )); then
    echo "Security Gate: FAIL"
    echo "Timed out while waiting for image scan."
    exit 1
  fi

  sleep "$POLL_INTERVAL_SECONDS"
  elapsed_seconds=$((elapsed_seconds + POLL_INTERVAL_SECONDS))
done

critical_count="$(
  aws ecr describe-image-scan-findings \
    --repository-name "$REPOSITORY_NAME" \
    --image-id imageTag="$IMAGE_TAG" \
    --region "$AWS_REGION" \
    --query 'imageScanFindings.findingSeverityCounts.CRITICAL || `0`' \
    --output text
)"

high_count="$(
  aws ecr describe-image-scan-findings \
    --repository-name "$REPOSITORY_NAME" \
    --image-id imageTag="$IMAGE_TAG" \
    --region "$AWS_REGION" \
    --query 'imageScanFindings.findingSeverityCounts.HIGH || `0`' \
    --output text
)"

medium_count="$(
  aws ecr describe-image-scan-findings \
    --repository-name "$REPOSITORY_NAME" \
    --image-id imageTag="$IMAGE_TAG" \
    --region "$AWS_REGION" \
    --query 'imageScanFindings.findingSeverityCounts.MEDIUM || `0`' \
    --output text
)"

low_count="$(
  aws ecr describe-image-scan-findings \
    --repository-name "$REPOSITORY_NAME" \
    --image-id imageTag="$IMAGE_TAG" \
    --region "$AWS_REGION" \
    --query 'imageScanFindings.findingSeverityCounts.LOW || `0`' \
    --output text
)"

echo ""
echo "========================================"
echo "Vulnerability Summary"
echo "========================================"
echo "CRITICAL: $critical_count"
echo "HIGH:     $high_count"
echo "MEDIUM:   $medium_count"
echo "LOW:      $low_count"
echo "========================================"

if [[ "$critical_count" != "0" || "$high_count" != "0" ]]; then
  echo "Security Gate: FAIL"
  echo "Critical or High vulnerabilities were detected."
  exit 1
fi

echo "Security Gate: PASS"
echo "No Critical or High vulnerabilities were detected."

Lưu file:

CTRL + O
ENTER
CTRL + X

Cấp quyền thực thi:

chmod +x scripts/ecr-security-gate.sh

9. Kiểm tra script

Chạy script với image đã tồn tại:

./scripts/ecr-security-gate.sh \
  online-boutique-frontend \
  a81c92f

Thay a81c92f bằng image tag thực tế.

Ví dụ kết quả:

========================================
ECR Security Gate
========================================
Repository: online-boutique-frontend
Image tag:  a81c92f
Region:     ap-northeast-1
========================================

Scan status: COMPLETE

========================================
Vulnerability Summary
========================================
CRITICAL: 0
HIGH:     0
MEDIUM:   3
LOW:      7
========================================

Security Gate: PASS
No Critical or High vulnerabilities were detected.

Nếu có vulnerability mức CRITICAL hoặc HIGH:

Security Gate: FAIL
Critical or High vulnerabilities were detected.

Kiểm tra exit code:

echo $?

Kết quả:

0

nghĩa là Security Gate thành công.

Kết quả:

1

nghĩa là Security Gate thất bại.


10. Xem chi tiết vulnerability

Để xem danh sách vulnerability:

aws ecr describe-image-scan-findings \
  --repository-name online-boutique-frontend \
  --image-id imageTag=a81c92f \
  --query 'imageScanFindings.findings[].{Severity:severity,Name:name,Description:description}' \
  --output table

Thay a81c92f bằng image tag thực tế.

Chỉ xem các vulnerability mức CRITICAL:

aws ecr describe-image-scan-findings \
  --repository-name online-boutique-frontend \
  --image-id imageTag=a81c92f \
  --query 'imageScanFindings.findings[?severity==`CRITICAL`].{Name:name,Description:description}' \
  --output table

Chỉ xem các vulnerability mức HIGH:

aws ecr describe-image-scan-findings \
  --repository-name online-boutique-frontend \
  --image-id imageTag=a81c92f \
  --query 'imageScanFindings.findings[?severity==`HIGH`].{Name:name,Description:description}' \
  --output table

11. Thêm Security Gate vào GitHub repository

Kiểm tra file reusable workflow:

nano .github/workflows/reusable-ci-cd.yml

Sau bước Push Docker image, thêm step:

      - name: Run ECR security gate
        env:
          AWS_REGION: ${{ vars.AWS_REGION }}
          ECR_REPOSITORY: ${{ inputs.ecr_repository }}
          IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
        run: |
          chmod +x scripts/ecr-security-gate.sh

          ./scripts/ecr-security-gate.sh \
            "$ECR_REPOSITORY" \
            "$IMAGE_TAG"

Ví dụ flow của job:

jobs:
  build:
    runs-on: ubuntu-latest

    permissions:
      contents: read

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Generate image tag
        id: image-tag
        shell: bash
        run: |
          SHORT_SHA="${GITHUB_SHA::7}"
          echo "tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT"

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ${{ vars.AWS_REGION }}
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Login to Amazon ECR
        id: ecr-login
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build Docker image
        env:
          ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
          ECR_REPOSITORY: ${{ inputs.ecr_repository }}
          IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
          SERVICE_PATH: ${{ inputs.service_path }}
        run: |
          docker build \
            -t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \
            "$SERVICE_PATH"

      - name: Push Docker image
        env:
          ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
          ECR_REPOSITORY: ${{ inputs.ecr_repository }}
          IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
        run: |
          docker push \
            "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: Run ECR security gate
        env:
          AWS_REGION: ${{ vars.AWS_REGION }}
          ECR_REPOSITORY: ${{ inputs.ecr_repository }}
          IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
        run: |
          chmod +x scripts/ecr-security-gate.sh

          ./scripts/ecr-security-gate.sh \
            "$ECR_REPOSITORY" \
            "$IMAGE_TAG"

Giữ nguyên các phần khác trong workflow hiện tại. Chỉ bổ sung step Run ECR security gate sau bước push image.


12. Kiểm tra GitHub Actions variables

Workflow sử dụng:

${{ vars.AWS_REGION }}

Vào GitHub repository:

Settings
   ↓
Secrets and variables
   ↓
Actions
   ↓
Variables

Tạo variable:

Name:
AWS_REGION

Value:
ap-northeast-1

Kiểm tra các secrets cần có:

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY

Nếu workflow hiện tại đã sử dụng các secrets này ở Chapter 4D thì không cần tạo lại.


13. Commit thay đổi

Kiểm tra file đã thay đổi:

git status

Kiểm tra nội dung thay đổi:

git diff

Thêm file:

git add \
  scripts/ecr-security-gate.sh \
  .github/workflows/reusable-ci-cd.yml

Commit:

git commit -m "add ECR security gate"

Push lên GitHub:

git push origin main

14. Kiểm tra GitHub Actions

Mở repository trên GitHub:

Actions
   ↓
Workflow
   ↓
Latest run

Kiểm tra các step:

Checkout source
    ↓
Generate image tag
    ↓
Configure AWS credentials
    ↓
Login to Amazon ECR
    ↓
Build Docker image
    ↓
Push Docker image
    ↓
Run ECR security gate

Nếu image đạt điều kiện:

Run ECR security gate
        ↓
    Security Gate: PASS
        ↓
       SUCCESS

Nếu image có vulnerability mức CRITICAL hoặc HIGH:

Run ECR security gate
        ↓
    Security Gate: FAIL
        ↓
       FAILURE

Khi step Security Gate thất bại, các step phía sau sẽ không được thực hiện.


15. Kiểm tra bằng cách tạo một workflow test

Để kiểm tra logic fail của pipeline, có thể tạo một workflow test riêng.

Tạo file:

nano .github/workflows/security-gate-test.yml

Thêm:

name: Security Gate Test

on:
  workflow_dispatch:

jobs:
  test-security-gate:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ${{ vars.AWS_REGION }}
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Run security gate
        env:
          AWS_REGION: ${{ vars.AWS_REGION }}
        run: |
          chmod +x scripts/ecr-security-gate.sh

          ./scripts/ecr-security-gate.sh \
            online-boutique-frontend \
            a81c92f

Thay a81c92f bằng image tag thực tế.

Commit và push:

git add .github/workflows/security-gate-test.yml
git commit -m "add security gate test workflow"
git push origin main

Chạy thủ công:

GitHub
   ↓
Actions
   ↓
Security Gate Test
   ↓
Run workflow

16. Áp dụng cho nhiều service

Trong workflow gọi reusable workflow, các service có thể sử dụng chung Security Gate:

jobs:
  frontend:
    uses: ./.github/workflows/reusable-ci-cd.yml
    with:
      service: frontend
      service_path: ./src/frontend
      ecr_repository: online-boutique-frontend
    secrets: inherit

  cartservice:
    uses: ./.github/workflows/reusable-ci-cd.yml
    with:
      service: cartservice
      service_path: ./src/cartservice
      ecr_repository: online-boutique-cartservice
    secrets: inherit

  paymentservice:
    uses: ./.github/workflows/reusable-ci-cd.yml
    with:
      service: paymentservice
      service_path: ./src/paymentservice
      ecr_repository: online-boutique-paymentservice
    secrets: inherit

Mỗi service sẽ có flow riêng:

frontend
   ↓
Build
   ↓
Push ECR
   ↓
Security Gate
cartservice
   ↓
Build
   ↓
Push ECR
   ↓
Security Gate
paymentservice
   ↓
Build
   ↓
Push ECR
   ↓
Security Gate

Nhưng tất cả đều sử dụng cùng một script:

scripts/ecr-security-gate.sh

17. Kết quả cuối cùng

Sau Chapter 4E, pipeline sẽ trở thành:

Developer
    ↓
GitHub Push
    ↓
GitHub Actions
    ↓
Reusable Workflow
    ↓
Test
    ↓
Docker Build
    ↓
Git SHA Tag
    ↓
Push Image lên ECR
    ↓
ECR Vulnerability Scan
    ↓
Security Gate
    ├── CRITICAL/HIGH → FAIL
    │                    ↓
    │                 Stop Pipeline
    │
    └── Không có CRITICAL/HIGH
                         ↓
                       PASS
                         ↓
                   Cho phép bước sau

18. Kiểm tra hoàn thành

[ ] Đã bật Scan on Push cho ECR

[ ] Đã push image mới lên ECR

[ ] Đã kiểm tra trạng thái scan

[ ] Đã xem vulnerability findings

[ ] Đã tạo ecr-security-gate.sh

[ ] Script trả về exit code 0 khi PASS

[ ] Script trả về exit code 1 khi FAIL

[ ] Đã thêm Security Gate vào reusable workflow

[ ] GitHub Actions chờ kết quả scan

[ ] Pipeline dừng khi có CRITICAL hoặc HIGH

[ ] Đã áp dụng cho nhiều Online Boutique services

Kết luận

Ở Chapter 4D, chúng ta đảm bảo:

Một tag chỉ đại diện cho một image cụ thể

Ở Chapter 4E, chúng ta bổ sung:

Không phải image nào cũng được phép đi tiếp

Nguyên tắc mới:

Build artifact một lần, lưu trữ bằng immutable tag, scan trước khi release và chỉ cho phép artifact đạt security gate đi tiếp.

Phần tiếp theo có thể xây dựng Chapter 4F — Artifact Promotion: Promote cùng một image từ Dev → Staging → Production mà không build lại.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí