diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index e79a2de..3222527 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,25 +1,16 @@ name: 'Setup Node + pnpm' description: 'Sets up Node.js (LTS by default) with pnpm and dependency caching.' -inputs: - node-auth-token: - description: 'Optional token exposed as NODE_AUTH_TOKEN to pnpm-related steps. Defaults to github.token.' - required: false - default: '' runs: using: 'composite' steps: - name: Install pnpm uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # 6.0.8 - env: - NODE_AUTH_TOKEN: ${{ inputs.node-auth-token || github.token }} with: run_install: false - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # 6.4.0 - env: - NODE_AUTH_TOKEN: ${{ inputs.node-auth-token || github.token }} with: node-version-file: .node-version cache: 'pnpm' @@ -27,6 +18,4 @@ runs: - name: Install dependencies shell: bash - env: - NODE_AUTH_TOKEN: ${{ inputs.node-auth-token || github.token }} run: pnpm install --frozen-lockfile diff --git a/.github/workflows/release-reusable.yml b/.github/workflows/release-reusable.yml index 3af3517..1c8dd00 100644 --- a/.github/workflows/release-reusable.yml +++ b/.github/workflows/release-reusable.yml @@ -3,16 +3,6 @@ name: Release (reusable) on: workflow_call: inputs: - npm-scope: - description: 'npm scope to authenticate (e.g. @kin0992). Leave empty for tag-only consumers that do not publish to a registry.' - required: false - type: string - default: '' - registry-url: - description: 'npm registry to authenticate against.' - required: false - type: string - default: 'https://registry.npmjs.org' provenance: description: 'Publish with npm provenance (requires id-token: write and a public registry).' required: false @@ -25,9 +15,6 @@ on: app_private_key: description: 'GitHub App private key (PEM).' required: true - npm_token: - description: 'Token for the target npm registry (e.g. an npmjs.org automation token). Required only when npm-scope is set.' - required: false permissions: contents: read @@ -66,23 +53,12 @@ jobs: - name: Build run: pnpm turbo run build - - name: Configure npm scope auth - if: ${{ inputs.npm-scope != '' }} - env: - NPM_SCOPE: ${{ inputs.npm-scope }} - REGISTRY_URL: ${{ inputs.registry-url }} - run: | - set -euo pipefail - host="${REGISTRY_URL#https://}" - host="${host#http://}" - host="${host%/}" - # Write the token as an unexpanded ${NODE_AUTH_TOKEN} reference so the - # secret is never persisted to disk; npm/pnpm interpolate it from the - # environment at publish time. Runs after Build so no earlier pnpm - # invocation sees the unresolved placeholder. - echo "${NPM_SCOPE}:registry=${REGISTRY_URL}" >> .npmrc - echo "//${host}/:_authToken=\${NODE_AUTH_TOKEN}" >> .npmrc - + # Publishing uses npm Trusted Publishing (OIDC): no npm token is written to + # disk or exposed in the environment. npm exchanges this workflow's id-token + # for a short-lived registry credential and signs provenance automatically. + # Each package must have a Trusted Publisher configured on npmjs.org that + # points at this repo's release.yml workflow, and npm must be >= 11.5.1 + # (satisfied by the Node version in .node-version). - name: Create release PR or publish uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b # 1.8.0 with: @@ -92,5 +68,4 @@ jobs: title: 'Release new artifacts' env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - NODE_AUTH_TOKEN: ${{ secrets.npm_token }} NPM_CONFIG_PROVENANCE: ${{ inputs.provenance }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5aba130..ae0dc20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,8 +19,6 @@ jobs: permissions: contents: write pull-requests: write - id-token: write # required for npm provenance + id-token: write # required for Trusted Publishing + provenance uses: ./.github/workflows/release-reusable.yml - with: - npm-scope: '@kin0992' secrets: inherit diff --git a/CONSUMING.md b/CONSUMING.md index 6e61610..9a583ad 100644 --- a/CONSUMING.md +++ b/CONSUMING.md @@ -69,10 +69,9 @@ jobs: Consumers call the reusable workflow `release-reusable.yml`. `release.yml` in this repo is a thin local entry-point — do not reference it from another repository. -There are two modes: +Publishing uses **npm Trusted Publishing** (OIDC): the workflow exchanges the job's `id-token` for a short-lived npmjs.org credential and npm signs **provenance** automatically. No npm token is stored or passed. Each published package needs a Trusted Publisher configured on npmjs.org (see Prerequisites). -- **Publishing** — set `npm-scope` and provide the `npm_token` secret. The workflow authenticates to the registry and your `release` script publishes packages. -- **Tag-only** — omit `npm-scope` (and `npm_token`). The npm auth step is skipped entirely, so a repo whose `release` script only runs e.g. `changeset tag` needs no npm credentials. +If your `release` script only creates git tags (e.g. `changeset tag`) and never runs `npm publish`, none of the npm / Trusted-Publishing setup applies. ```yaml name: Release @@ -85,33 +84,26 @@ jobs: permissions: contents: write pull-requests: write - id-token: write # required for npm provenance + id-token: write # required for Trusted Publishing + provenance uses: kin0992/dev-toolkit/.github/workflows/release-reusable.yml@main - with: - npm-scope: '@yourscope' - # registry-url defaults to https://registry.npmjs.org - # provenance defaults to true (needs id-token: write and a public registry) + # provenance defaults to true; pass `provenance: false` to opt out secrets: app_id: ${{ secrets.APP_ID }} app_private_key: ${{ secrets.APP_PRIVATE_KEY }} - npm_token: ${{ secrets.NPM_TOKEN }} ``` **Inputs:** -| Input | Required | Default | Description | -| -------------- | -------- | ---------------------------- | ---------------------------------------------------------------- | -| `npm-scope` | no | `''` | npm scope to authenticate (e.g. `@yourscope`). Empty = tag-only. | -| `registry-url` | no | `https://registry.npmjs.org` | Registry the scope is authenticated against. | -| `provenance` | no | `true` | Publish with npm provenance (needs `id-token: write`). | +| Input | Required | Default | Description | +| ------------ | -------- | ------- | ------------------------------------------------------ | +| `provenance` | no | `true` | Publish with npm provenance (needs `id-token: write`). | **Secrets:** -| Secret | Required | Description | -| ----------------- | -------- | --------------------------------------------------------------------- | -| `app_id` | yes | GitHub App client ID used to mint a token for git ops. | -| `app_private_key` | yes | GitHub App private key (PEM). | -| `npm_token` | no | Token for the target registry. Required only when `npm-scope` is set. | +| Secret | Required | Description | +| ----------------- | -------- | ------------------------------------------------------ | +| `app_id` | yes | GitHub App client ID used to mint a token for git ops. | +| `app_private_key` | yes | GitHub App private key (PEM). | **Prerequisites:** @@ -120,7 +112,7 @@ jobs: - Create a GitHub App with `contents: write` and `pull-requests: write` permissions on your repository. - Add `APP_ID` (the numeric App ID — used as `client-id`) and `APP_PRIVATE_KEY` (the PEM private key) as repository secrets. - Install the GitHub App on the repository. -- For publishing to npmjs.org, generate an **Automation** access token for an account that owns (or is a member of) the target scope and add it as the `NPM_TOKEN` repository secret. +- For publishing to npmjs.org, configure **Trusted Publishing** once per package: on npmjs.org open the package → **Settings → Trusted Publisher → GitHub Actions**, and set the owner/repo to your consumer repo and the workflow filename to your release workflow (e.g. `release.yml`). No npm token is needed. (A brand-new package may need an initial token-based publish before a trusted publisher can be attached — see npm's docs.) - Provenance requires the repository to be **public** and the job to grant `id-token: write`. Each published package should set `"publishConfig": { "access": "public", "provenance": true }`. ### Security analysis (CodeQL + secret scan + pnpm audit) diff --git a/infra/Pulumi.prod.yaml b/infra/Pulumi.prod.yaml index e3c99be..0bf157a 100644 --- a/infra/Pulumi.prod.yaml +++ b/infra/Pulumi.prod.yaml @@ -2,7 +2,5 @@ config: devToolkit:repoName: dev-toolkit devToolkit:defaultBranch: main github:owner: kin0992 - devToolkit:npmToken: - secure: AAABAJBpCXqVz2KYKQtgTPB4oEUkgDOLc1AhEHGhogxUi8vnCc6VjyIyIR0ilZhWdozxWL2eByCJ+w8dW9o/YyJ8o2j/XADZ devToolkit:pulumiAccessToken: secure: AAABAFzzntAVe+s+2gBaaraLf9IrltK0s4ReAMCJ/FTm3/37kuSOyzOeZQPZR0GetzDO9Oj1A+i6qaJn53h6A0ct9lEmbIitL7Ap5Q== diff --git a/infra/index.ts b/infra/index.ts index 6cabb0d..7d020ac 100644 --- a/infra/index.ts +++ b/infra/index.ts @@ -3,9 +3,6 @@ import * as github from '@pulumi/github'; const cfg = new pulumi.Config('devToolkit'); const repoName = cfg.get('repoName') ?? 'dev-toolkit'; -// npmjs.org Automation token consumed by the release workflow to publish -// @kin0992/* packages with provenance. -const npmToken = cfg.requireSecret('npmToken'); // Pulumi Cloud access token consumed by the IaC drift/deploy workflows to // authenticate the Pulumi CLI in non-interactive CI runs. const pulumiAccessToken = cfg.requireSecret('pulumiAccessToken'); @@ -82,12 +79,6 @@ new github.RepositoryDependabotSecurityUpdates('dependabot-updates', { enabled: true, }); -new github.ActionsSecret('npm-token', { - repository: repo.name, - secretName: 'NPM_TOKEN', - value: npmToken, -}); - new github.ActionsSecret('pulumi-access-token', { repository: repo.name, secretName: 'PULUMI_ACCESS_TOKEN',