Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 7 — GitHub Actions — Secrets & Environments

Đã đọc 0/14 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa viết xong một workflow GitHub Actions tuyệt đẹp: code được test, build thành công, rồi tự động deploy lên server production. Nhưng khoan đã — để deploy được, workflow cần biết mật khẩu database, cần token để đăng nhập vào AWS, cần API key để gọi dịch vụ thanh toán. Câu hỏi sống còn là: những thông tin nhạy cảm ấy nằm ở đâu?

Nếu câu trả lời của bạn là "em để trong file YAML luôn cho tiện" thì đây chính là bài học quan trọng nhất bạn cần đọc. Bởi vì một secret bị lộ có thể khiến cả công ty trả giá bằng hàng chục nghìn đô la, dữ liệu khách hàng bị đánh cắp, hoặc hạ tầng bị chiếm quyền. Trong thực tế, các bot quét GitHub liên tục 24/7 để tìm những dòng như AWS_SECRET_KEY=AKIA... bị commit nhầm — và chúng khai thác chỉ trong vài phút.

Ở Bài 6, chúng ta đã học cách viết workflow và action cơ bản. Bài 7 này sẽ trả lời câu hỏi tiếp theo và cũng là câu hỏi khiến nhiều bạn "vấp" nhất khi bắt đầu làm CI/CD nghiêm túc: làm sao đưa thông tin bí mật vào pipeline một cách an toàn, và làm sao kiểm soát việc deploy lên từng môi trường (staging, production) một cách có kỷ luật? Đây là hai trụ cột — Secrets và Environments — giúp pipeline của bạn vừa chạy được, vừa không trở thành lỗ hổng bảo mật cho cả tổ chức.

Khái niệm cốt lõi

Nguyên tắc số một: Secret không bao giờ nằm trong code

Quy tắc bất di bất dịch: mọi thông tin nhạy cảm phải tách rời khỏi mã nguồn. Password, token, API key, private SSH key, connection string — tất cả không được viết trực tiếp vào file .yml, không được commit vào Git. Lý do đơn giản: lịch sử Git là vĩnh viễn. Ngay cả khi bạn xóa secret ở commit sau, nó vẫn còn nằm trong lịch sử và ai có quyền đọc repo đều có thể moi ra.

GitHub Actions giải quyết vấn đề này bằng cơ chế Secrets — một kho lưu trữ được mã hóa, tách biệt hoàn toàn khỏi code. Bạn khai báo secret một lần trong giao diện GitHub (hoặc qua CLI), sau đó tham chiếu tới nó trong workflow bằng cú pháp ${{ secrets.TEN_SECRET }}. GitHub tự động mã hóa lúc lưu và chỉ giải mã khi runner cần dùng.

Ba cấp độ của Secrets

GitHub Actions cung cấp secret ở nhiều phạm vi khác nhau, và hiểu rõ sự khác biệt này rất quan trọng:

1. Repository secrets — Secret dùng chung cho mọi workflow trong một repo. Đây là loại phổ biến nhất. Ví dụ, một repo có DOCKER_PASSWORD để đẩy image lên registry. Bất kỳ workflow nào trong repo đó đều truy cập được. Đơn giản, nhưng cũng có nghĩa là mọi workflow đều "thấy" nó.

2. Environment secrets — Secret gắn với một môi trường cụ thể (ví dụ production, staging). Đây là điểm mấu chốt khiến Secrets và Environments đi liền với nhau. Bạn có thể có một secret tên DATABASE_URL với giá trị khác nhau cho staging và production. Workflow chỉ lấy được giá trị đúng khi nó khai báo environment: production. Nhờ vậy, không có chuyện job build ngẫu nhiên chạm được vào mật khẩu database production.

3. Organization secrets — Secret dùng chung cho nhiều repo trong cùng một tổ chức. Ví dụ công ty có 30 repo microservice cùng đẩy image lên một registry chung — thay vì khai báo REGISTRY_TOKEN 30 lần, bạn khai báo một lần ở cấp organization và chọn những repo được phép dùng. Rất tiện cho việc quản lý tập trung và xoay vòng (rotate) token.

Ngoài ra còn có Variables — tương tự secret nhưng dành cho dữ liệu KHÔNG nhạy cảm (ví dụ tên vùng AWS ap-southeast-1, tên image, cấu hình bật/tắt tính năng). Variables được hiển thị plaintext trong log, dùng cú pháp ${{ vars.TEN_BIEN }}. Đừng nhồi thông tin không bí mật vào secret — nó khiến log khó debug vì bị che mất.

Environments — kiểm soát việc deploy có kỷ luật

Environment trong GitHub Actions không chỉ là chỗ chứa secret. Nó là một "cổng kiểm soát" (deployment gate) cho phép bạn đặt ra luật chơi trước khi job được phép chạy lên môi trường đó:

  • Required reviewers: Yêu cầu một hoặc nhiều người phê duyệt thủ công trước khi deploy. Job sẽ "treo" chờ ai đó bấm nút Approve. Cực kỳ hữu ích cho production.
  • Wait timer: Bắt job chờ N phút trước khi chạy — tạo khoảng đệm để kịp hủy nếu phát hiện sai sót.
  • Deployment branches: Giới hạn chỉ những branch nhất định (ví dụ chỉ main) mới được deploy vào environment này. Không cho phép deploy production từ một feature branch bất kỳ.
Khi một job khai báo environment: production, nó tự động thừa hưởng cả các luật bảo vệ lẫn các environment secret của môi trường đó.

Cách secret được bảo vệ trong log

GitHub tự động masking (che) giá trị secret trong log. Nếu secret của bạn là mySecretPass123 và nó vô tình xuất hiện trong output, GitHub sẽ thay bằng *. Tuy nhiên đây là lưới an toàn cuối cùng, không phải lá chắn tuyệt đối — nếu bạn echo một secret đã bị base64 hóa hoặc biến đổi, masking có thể không nhận ra. Nguyên tắc: đừng bao giờ chủ động in secret ra.

Tình huống thực tế

Tình huống 1: Startup fintech tại TP.HCM và sự cố lộ token

Một startup fintech ở Quận 1, TP.HCM (khoảng 25 kỹ sư) đang xây dựng ví điện tử. Trong giai đoạn đầu, một dev junior để STRIPE_SECRET_KEY thẳng trong file deploy.yml "cho nhanh" rồi push lên một repo tưởng là private nhưng thực ra được set public do cấu hình nhầm.

Chỉ trong vòng khoảng 8 phút, một bot đã tìm thấy key và bắt đầu gọi API Stripe. May mắn là đây là test key ở môi trường sandbox nên thiệt hại bằng tiền là 0, nhưng nó gióng lên hồi chuông cảnh tỉnh. Đội ngũ ngay lập tức revoke key, chuyển toàn bộ sang Environment secrets, tách riêng production và staging, và bật required reviewer cho môi trường production — bắt buộc tech lead phê duyệt mọi lần deploy.

Bài học: Secret trong code không phải là "sẽ bị lộ hay không" mà là "bị lộ sau bao lâu". Việc tách secret ra environment và thêm reviewer đã biến một thảm họa tiềm tàng thành một quy trình có kiểm soát. Nếu đó là live key, con số thiệt hại có thể lên tới hàng trăm triệu đồng trong vài giờ.

Tình huống 2: Công ty thương mại điện tử với 40 microservice

Một sàn thương mại điện tử tại Hà Nội có khoảng 40 microservice, mỗi service một repo riêng. Ban đầu mỗi repo tự khai báo HARBOR_REGISTRY_TOKEN để đẩy Docker image. Khi công ty phải xoay vòng token registry theo chính sách bảo mật hằng quý, đội DevOps phải sửa thủ công 40 nơi — mất gần cả ngày và có lần bỏ sót 3 repo khiến pipeline gãy hàng loạt sáng thứ Hai.

Giải pháp: chuyển HARBOR_REGISTRY_TOKEN lên Organization secret, cấp quyền cho đúng các repo cần dùng. Từ đó mỗi lần rotate chỉ cần cập nhật một chỗ duy nhất, tất cả 40 service nhận giá trị mới ngay lập tức. Thời gian rotate giảm từ 6 tiếng xuống còn 5 phút.

Bài học: Khi secret được chia sẻ giữa nhiều repo, organization secret không chỉ tiện mà còn giảm rủi ro sai sót con người. Quản lý tập trung là điều kiện tiên quyết để bảo mật ở quy mô lớn.

Tình huống 3: Đội nhỏ và cạm bẫy "pull_request từ fork"

Một team làm SaaS 6 người có một dự án open-source phụ. Họ nhận được một pull request từ contributor bên ngoài (từ một fork). Điều họ không biết: theo mặc định, GitHub không truyền secret vào workflow chạy từ PR của fork — chính là để chống việc kẻ xấu gửi PR chứa code độc echo ${{ secrets.PROD_TOKEN }} để trộm secret.

Ban đầu đội tưởng pipeline "bị lỗi" vì secret rỗng, suýt nữa nới lỏng cấu hình để "cho nó chạy". May mắn một mentor cảnh báo kịp: nếu bật pull_request_target sai cách và checkout code của fork, họ sẽ tự mở toang cánh cửa cho attacker. Họ chuyển sang tách rõ: các job cần secret chỉ chạy khi PR đã được merge vào branch nội bộ, còn job kiểm tra code của fork thì chạy trong môi trường không có secret.

Bài học: Secret và mô hình tin cậy (trust boundary) luôn đi cùng nhau. Hiểu rõ khi nào secret được truyền, khi nào không, giúp bạn tránh cả việc pipeline "gãy vô cớ" lẫn việc tạo lỗ hổng chết người.

Hướng dẫn từng bước

Hãy cùng thiết lập một pipeline deploy có kiểm soát, dùng cả secret lẫn environment.

Bước 1 — Tạo Repository secret. Vào repo trên GitHub, chọn Settings → Secrets and variables → Actions → New repository secret. Đặt tên (ví dụ DOCKER_PASSWORD, quy ước viết HOA_CÓ_GẠCH_DƯỚI) và dán giá trị. Bấm Add. Từ giờ giá trị này được mã hóa, bạn không thể xem lại — chỉ có thể ghi đè.

Bước 2 — Tạo Environment. Vào Settings → Environments → New environment, đặt tên production. Trong màn hình cấu hình, bật Required reviewers và thêm chính bạn hoặc tech lead. Có thể bật thêm Deployment branches giới hạn chỉ main.

Bước 3 — Thêm Environment secret. Ngay trong trang cấu hình environment production, thêm secret DATABASE_URL với giá trị của database production. Tạo tiếp environment staging với DATABASE_URL khác. Cùng một tên secret, hai giá trị — workflow tự chọn đúng theo môi trường.

Bước 4 — Tham chiếu trong workflow.

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production        # kích hoạt gate + secret của production
    steps:
      - uses: actions/checkout@v4

      - name: Login Docker Registry
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u myuser --password-stdin

      - name: Run migration
        run: ./migrate.sh
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}   # lấy đúng giá trị của production

Bước 5 — Xác nhận luồng phê duyệt. Push lên main, vào tab Actions. Bạn sẽ thấy job deploy treo ở trạng thái Waiting kèm nút Review deployments. Reviewer bấm Approve thì job mới chạy. Đây chính là kỷ luật deploy mà bạn vừa tạo ra.

Bước 6 — Kiểm tra masking. Xem log, bạn sẽ thấy ${{ secrets.DOCKER_PASSWORD }} được hiển thị là *. Đừng cố echo secret để "kiểm tra" — hãy tin vào cơ chế.

Lỗi thường gặp & mẹo

Lỗi 1 — Hardcode secret rồi mới nghĩ tới việc dời ra. Nhiều bạn "để tạm" trong YAML rồi quên. Một khi đã commit, secret coi như đã lộ — bắt buộc phải revoke và tạo mới, không phải chỉ xóa dòng đó. Xóa khỏi code không xóa khỏi lịch sử Git.

Lỗi 2 — Truyền secret vào workflow của fork. Đừng dùng pull_request_target kết hợp checkout code fork một cách bừa bãi. Đây là lỗ hổng bị khai thác nhiều nhất. Chỉ chạy job cần secret trên code bạn tin tưởng.

Lỗi 3 — In secret ra log để debug. echo ${{ secrets.X }} là thói quen nguy hiểm. Nếu bạn biến đổi secret (base64, cắt chuỗi) rồi in, masking có thể thất bại.

Lỗi 4 — Nhầm secret với variable. Đừng nhét cấu hình không bí mật (region, tên bucket) vào secret — nó bị che thành * khiến log vô dụng khi debug. Dùng vars. cho dữ liệu công khai, secrets. cho dữ liệu bí mật.

Lỗi 5 — Environment secret nhưng quên khai báo environment:. Nếu job không có dòng environment: production, nó sẽ KHÔNG thấy environment secret và bạn nhận giá trị rỗng. Đây là lỗi "im lặng" gây đau đầu nhất.

Mẹo hay:

  • Đặt tên secret theo môi trường rõ ràng nếu để chung repo: nhưng tốt hơn là dùng environment để tách.
  • Bật required reviewer cho production ngay từ ngày đầu, kể cả team nhỏ — nó rẻ mà cứu bạn khỏi những cú deploy nhầm lúc 2h sáng.
  • Định kỳ rotate secret, và ưu tiên dùng OIDC (OpenID Connect) để cấp quyền tạm thời cho cloud thay vì lưu long-lived key — nhưng đó là chủ đề nâng cao ta sẽ chạm tới ở các bài về bảo mật pipeline.
  • Dùng CLI cho nhanh: gh secret set DOCKER_PASSWORD giúp thêm secret không cần mở trình duyệt.

Bài tập thực hành

  • Thiết lập cơ bản: Tạo một repo test, thêm một repository secret tên GREETING_TOKEN. Viết workflow in ra độ dài của secret (không in giá trị) để chứng minh nó đã được truyền vào — ví dụ dùng echo ${#TOKEN} với TOKEN được gán từ secret qua env.
  • Hai môi trường: Tạo environment staging và production, mỗi cái có secret API_ENDPOINT với giá trị khác nhau. Viết hai job, mỗi job trỏ tới một environment, và xác nhận chúng in ra endpoint khác nhau (che giá trị, chỉ so sánh bằng cách in ký tự đầu).
  • Gate phê duyệt: Bật required reviewer cho production. Trigger một lần deploy và thực hành luồng Approve/Reject trong tab Actions. Ghi lại: job treo bao lâu, ai được phép duyệt.
  • Suy ngẫm bảo mật: Viết ra 3 secret mà một dự án e-commerce thực tế cần (ví dụ token thanh toán, mật khẩu DB, key gửi SMS OTP), và với mỗi cái, quyết định nên để ở cấp repository, environment hay organization — kèm lý do.

Tóm tắt

  • Secret không bao giờ nằm trong code. Đã commit là coi như lộ; phải revoke và tạo mới, không chỉ xóa dòng.
  • GitHub Actions có ba cấp secret: repository (chung cho repo), environment (theo từng môi trường), và organization (chung nhiều repo). Chọn đúng cấp giúp vừa an toàn vừa dễ quản lý.
  • Phân biệt secret (bí mật, bị mask) với variable (công khai, in ra log được).
  • Environment không chỉ chứa secret mà còn là cổng kiểm soát deploy: required reviewers, wait timer, deployment branches. Đây là cách tạo kỷ luật cho những lần deploy lên production.
  • Nhớ luôn khai báo environment: trong job để nhận đúng environment secret, và hiểu rõ mô hình tin cậy với PR từ fork để không tự mở lỗ hổng.
Nắm vững Secrets và Environments, bạn đã có nền tảng để mọi pipeline sau này — dù triển khai lên đâu — đều vừa chạy trơn tru vừa an toàn. Ở các bài tiếp theo, ta sẽ áp dụng chính những viên gạch này khi xây dựng pipeline nhiều môi trường và triển khai các chiến lược deploy nâng cao.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi