Workflow release chạy xanh mà plugin vẫn chưa được ký

Một lần đổi version trên main gắn tag, build, attest và ký plugin Sensor Floor Map ra sao. Thêm hai lỗ hổng CI bỏ sót: file zip 0.2.1 chưa ký, và bước ký chỉ hỏng sau khi merge.

Phan Minh Đăng Khoa
5 phút đọcAI viết · AI soát
Mục lục

dkhoa-dev/grafana-sensor-floor-map là plugin panel Grafana mã nguồn mở trong bài về sơ đồ tầng SVG. Từ ngày 26/10 đến 1/11/2026, plugin ra bốn bản: 0.1.0, 0.2.0, 0.2.1 và 0.2.2. Bài này ghi lại workflow build các bản đó, và hai chỗ khiến một lần chạy xanh vẫn cho ra file sai.

Đổi version là có tag

Trước đây muốn ra bản mới thì phải tự đẩy một tag v*. PR #26, merge ngày 26/10, chuyển việc đó sang version trong package.json. Workflow Tag release chạy mỗi khi có push lên main làm đổi file này:

# .github/workflows/tag-release.yml
tag="v$(jq -r .version package.json)"
if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${tag}" --silent 2>/dev/null; then
  echo "${tag} already exists, nothing to release."
  exit 0
fi
gh api "repos/${GITHUB_REPOSITORY}/git/refs" -f ref="refs/tags/${tag}" -f sha="${GITHUB_SHA}" --silent
gh workflow run release.yml --repo "${GITHUB_REPOSITORY}" --ref "${tag}"

Dòng cuối có lý do. Workflow Release chờ sự kiện push tag, nhưng tag tạo bằng GITHUB_TOKEN không kích hoạt workflow nào. Riêng sự kiện workflow_dispatch và repository_dispatch thì vẫn tạo workflow run. Vì vậy Release nhận thêm workflow_dispatch, và job gọi nó ngay trên ref của tag. Chạy trên tag thì bước so tag với version trong action build-plugin của Grafana vẫn qua.

  1. triggerĐổi versionpackage.json trên main
  2. tagTag releasetạo v<version>
  3. dispatchReleaseworkflow_dispatch trên tag
  4. buildbuild-pluginNode 24, ký, attest
  5. outputBản release nhápfile zip và SHA1
Một lần merge đổi version trong package.json là chạy hết quy trình release.

Attestation và chữ ký là hai thứ khác nhau

Khi review bản 0.2.0, Grafana khuyên thêm build provenance attestation. PR #29 bật attestation: true cho build-plugin và cấp cho job hai quyền id-token, attestations. Ai tải file zip về cũng đối chiếu được với repo:

gh attestation verify dkhoa-sensorfloormap-panel-0.2.2.zip --repo dkhoa-dev/grafana-sensor-floor-map

Lệnh này qua với file zip 0.2.2. Workflow ký là release.yml tại refs/tags/v0.2.2.

Attestation chỉ cho biết file zip được build ở đâu. Chữ ký Grafana nằm ở chỗ khác: file MANIFEST.txt trong zip, do sign-plugin thêm vào bằng access policy token. Trước ngày 1/11, input policy_token trong release.yml vẫn bị comment.

Release xanh, file zip chưa ký

Bản v0.2.1 chạy release mà không có token ký. Lần chạy xanh, nhưng file zip đăng lên không có MANIFEST.txt. Grafana đã review đúng file đó, nên build lại sẽ đưa cho bên review một file khác.

Ngày 1/11, một workflow dùng một lần trên branch sign/v0.2.1 ký thẳng file zip đã đăng. Nó tải file về, so SHA1 với bản đã review, và dừng nếu trong zip đã có MANIFEST.txt. Sau đó nó ký bằng đúng bản sign-plugin khoá trong lockfile của tag, nén lại và attest file mới. Cùng ngày, file của bản 0.2.1 được thay. Attestation của chúng ghi sign-release.yml trên branch đó.

Cho các bản sau, PR #36 truyền token vào bước build:

# .github/workflows/release.yml (comments removed)
- uses: grafana/plugin-actions/build-plugin@build-plugin/v1.2.1
  with:
    attestation: true
    node-version: '24'
    policy_token: ${{ secrets.GRAFANA_ACCESS_POLICY_TOKEN }}

src/release.test.ts đọc chính file này. Test đỏ khi bước build mất token, khi input cũ grafana_token quay lại, hoặc khi tắt attestation. Test cũng đỏ khi CHANGELOG.md thiếu tiêu đề cho version trong package.json. PR ghi lại sáu lần sửa thử, mỗi lần một chỗ, và mỗi lần đúng một test đỏ.

Bản nâng cấp chỉ hỏng trên main

PR #32 của Dependabot nâng @grafana/sign-plugin từ 3.3.3 lên 4.0.0. Bản 4 cần Node 24.5.0 trở lên, còn CI đang chạy Node 22. Lần chạy CI của PR vẫn xanh, vì bước ký trong ci.yml chỉ chạy khi có token:

# .github/workflows/ci.yml
- name: Sign plugin
  run: npm run sign
  if: ${{ env.GRAFANA_ACCESS_POLICY_TOKEN != '' }}

Workflow chạy từ pull request của Dependabot không được đọc Actions secret. Trên PR #32, bước ký bị bỏ qua. Merge xong ngày 1/11, lần chạy CI kế tiếp trên main đỏ ngay ở bước đó:

Node.js >=24.5.0 is required. Current: v22.23.4

PR #35 đưa mọi workflow lên Node 24. build-plugin mặc định chạy Node 20, nên release.yml giờ truyền node-version: '24'. Trường engines đổi thành >=24.5.0.

PR này thêm src/nodeVersion.test.ts. Test đọc khoảng engines.node của bản sign-plugin đang cài, rồi so từng node-version trong .github/workflows với khoảng đó. .nvmrc và engines cũng được so:

// src/nodeVersion.test.ts
const tooOld = versions.filter((v) => !semver.intersects(v.version, signRange));
expect(tooOld).toEqual([]);

Đây là unit test, không cần secret. Lần tới, bản sign-plugin nào nâng mức Node tối thiểu sẽ đỏ ngay trên pull request của nó. Khi PR thử trả về Node 22 ở một chỗ bất kỳ (một trong năm workflow, .nvmrc hoặc engines), đúng một test đỏ.

Bản 0.2.2

PR #36 cũng nâng version lên 0.2.2. Trước khi merge, bản build trên CI của PR đã được ký bằng cùng token, và @grafana/plugin-validator -strict, chạy với hai analyzer signature và manifest, không báo lỗi nào. PR merge ngày 1/11, Tag release tạo v0.2.2 rồi gọi Release trên tag đó. File dkhoa-sensorfloormap-panel-0.2.2.zip đã đăng có MANIFEST.txt ký PGP, signatureType: community, và attestation của nó qua kiểm tra.

Bài học

  • Bước nào nằm sau if: <secret> != '' thì không được test trên pull request không có secret, như PR của Dependabot.
  • Kiểm mức Node tối thiểu của một công cụ bằng unit test, vì unit test chạy trên mọi pull request.
  • Job release xanh chưa chứng minh file đã được ký. Cần test chính các input dùng để ký trong workflow.
  • Tag tạo bằng GITHUB_TOKEN không kích hoạt workflow nào. Gọi workflow tiếp theo bằng dispatch trên ref của tag.
  • Muốn ký một file đã được review thì ký đúng các byte đó, và so hash trước. Đừng build lại.
Phan Minh Đăng Khoa
Principal Engineer · AI, IoT và tự động hoá công nghiệp.
← Bài trướcSơ đồ tầng cũng là dữ liệu không tin được: làm sạch SVG trong một panel Grafana