diff --git a/.github/workflows/marketplace-identity.yml b/.github/workflows/marketplace-identity.yml index 6b38003..2be436e 100644 --- a/.github/workflows/marketplace-identity.yml +++ b/.github/workflows/marketplace-identity.yml @@ -2,6 +2,12 @@ name: Resolve Marketplace Identity on: workflow_dispatch: + inputs: + verify_marketplace_access: + description: Verify that the managed identity can publish for the configured Publisher + required: false + default: false + type: boolean permissions: contents: read @@ -55,3 +61,13 @@ jobs: '' ('`{0}`' -f $profile.id) ) | Add-Content -Path $env:GITHUB_STEP_SUMMARY + - name: Verify Marketplace publishing permission + if: ${{ inputs.verify_marketplace_access }} + shell: pwsh + env: + PUBLISHER: ${{ vars.VSCODE_MARKETPLACE_PUBLISHER }} + run: | + if ([string]::IsNullOrWhiteSpace($env:PUBLISHER)) { + throw 'Set the VSCODE_MARKETPLACE_PUBLISHER repository variable before verifying Marketplace access.' + } + npx --yes @vscode/vsce@3.9.2 verify-pat --azure-credential $env:PUBLISHER diff --git a/docs/RELEASING.md b/docs/RELEASING.md index b6918fd..fa21374 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -87,18 +87,22 @@ az role assignment create \ --scope "/subscriptions/$SUBSCRIPTION_ID" ``` -3. 为该身份创建 GitHub Environment 专用的联合凭据。Issuer、Subject 和 Audience 必须逐字一致;Environment 名称由工作流固定为 `marketplace-release`: +3. 查询仓库当前使用的 OIDC Subject 前缀,再为该身份创建 GitHub Environment 专用的联合凭据。Issuer、Subject 和 Audience 必须与 GitHub 实际签发的断言逐字一致;本仓库使用包含组织与仓库数字 ID 的稳定前缀,Environment 名称固定为 `marketplace-release`: ```bash +gh api repos/realSilasYang/CodeBookmark/actions/oidc/customization/sub + az identity federated-credential create \ --name GitHub-CodeBookmark-marketplace-release \ --identity-name CodeBookmarkMarketplacePublisher \ --resource-group CodeBookmark-Publishing \ --issuer https://token.actions.githubusercontent.com \ - --subject repo:realSilasYang/CodeBookmark:environment:marketplace-release \ + --subject repo:realSilasYang@64590265/CodeBookmark@1308408396:environment:marketplace-release \ --audiences api://AzureADTokenExchange ``` +不要根据仓库显示名称自行拼接 Subject。若 GitHub 返回的 `sub_claim_prefix` 与上例不同,应以实际返回值加上 `:environment:marketplace-release`;Azure 登录错误中的 `presented assertion subject` 也可以用于逐字核对。 + 4. 在 GitHub 仓库创建名为 `marketplace-release` 的 Environment。工作流将发布 Job 绑定到此 Environment,因而 OIDC 令牌的 Subject 只能匹配上面的联合凭据: ```bash @@ -121,14 +125,24 @@ gh variable set AZURE_TENANT_ID --repo realSilasYang/CodeBookmark --body "<托 gh variable set AZURE_SUBSCRIPTION_ID --repo realSilasYang/CodeBookmark --body "" ``` -6. 在 GitHub Actions 中手动运行 `Resolve Marketplace Identity` 工作流。它只登录 Entra、调用 Azure DevOps Profile API,并把非机密的 Marketplace identity resource ID 写入运行摘要,不会构建或发布扩展。也可以从终端触发: +6. 在 GitHub Actions 中手动运行 `Resolve Marketplace Identity` 工作流。它只登录 Entra、调用 Azure DevOps Profile API,并把非机密的 Marketplace identity resource ID 写入运行摘要,不会构建或发布扩展。首次运行不启用权限验证,也可以从终端触发: ```bash -gh workflow run marketplace-identity.yml --repo realSilasYang/CodeBookmark +gh workflow run marketplace-identity.yml \ + --repo realSilasYang/CodeBookmark \ + -f verify_marketplace_access=false ``` 7. 从该工作流的 Summary 复制 identity resource ID,在 [Visual Studio Marketplace 管理页](https://marketplace.visualstudio.com/manage)打开 Publisher `realSilasYang`,使用这个 ID 添加成员并授予 `Contributor` 角色。这里不能使用 Azure 门户中的资源名称、客户端 ID 或对象 ID 代替。只有该身份已经加入 Marketplace Publisher,`--azure-credential` 才有发布权限。 +8. 再次运行同一工作流并启用权限验证。它会调用 `vsce verify-pat --azure-credential` 验证托管身份确实具有 Publisher 发布权限,但不会上传或修改扩展;只有本次运行完整成功后,才创建正式版本标签: + +```bash +gh workflow run marketplace-identity.yml \ + --repo realSilasYang/CodeBookmark \ + -f verify_marketplace_access=true +``` + `AZURE_CLIENT_ID`、`AZURE_TENANT_ID` 和 `AZURE_SUBSCRIPTION_ID` 是资源标识,不是凭据。`azure/login` 在每次运行时使用 GitHub OIDC 换取短期令牌,`vsce` 再从已登录的 Azure CLI 会话取得该令牌。仓库不需要 Actions Secret;不要添加长期发布令牌或客户端密钥作为兼容路径。 配置完成后,可在创建正式标签前从 Actions 页面手动检查 Release 工作流配置,但不要用虚假版本标签测试发布。正式发布失败时先修正身份、变量或 Publisher 权限,再重新运行原工作流。 diff --git a/scripts/verify-release-readiness.js b/scripts/verify-release-readiness.js index 7ed8104..6ba2d65 100644 --- a/scripts/verify-release-readiness.js +++ b/scripts/verify-release-readiness.js @@ -175,13 +175,20 @@ const marketplaceIdentityWorkflow = read( path.join('.github', 'workflows', 'marketplace-identity.yml') ) assert.match(marketplaceIdentityWorkflow, /workflow_dispatch:/) +assert.match(marketplaceIdentityWorkflow, /verify_marketplace_access:/) assert.match(marketplaceIdentityWorkflow, /environment: marketplace-release/) assert.match(marketplaceIdentityWorkflow, /id-token: write/) assert.match(marketplaceIdentityWorkflow, /uses: azure\/login@v2/) assert.match(marketplaceIdentityWorkflow, /app\.vssps\.visualstudio\.com\/_apis\/profile\/profiles\/me/) assert.match(marketplaceIdentityWorkflow, /499b84ac-1321-427f-aa17-267ca6975798/) assert.match(marketplaceIdentityWorkflow, /GITHUB_STEP_SUMMARY/) +assert.match(marketplaceIdentityWorkflow, /@vscode\/vsce@3\.9\.2 verify-pat --azure-credential/) +assert.match(marketplaceIdentityWorkflow, /VSCODE_MARKETPLACE_PUBLISHER/) assert.doesNotMatch(marketplaceIdentityWorkflow, /VSCE_PAT|secrets\.|--pat\b/) +assert.match( + read(path.join('docs', 'RELEASING.md')), + /repo:realSilasYang@64590265\/CodeBookmark@1308408396:environment:marketplace-release/ +) assert.match(releaseWorkflow, /tags:[\s\S]*- v\*/) assert.match(releaseWorkflow, /GITHUB_REF_NAME/) assert.match(releaseWorkflow, /VSCODE_MARKETPLACE_PUBLISHER/)