diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47fd4f7..4655cae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,8 +23,12 @@ concurrency: jobs: refresh-token: + if: github.ref_name == github.event.repository.default_branch runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: write + id-token: write steps: - name: Checkout Code uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 @@ -46,11 +50,16 @@ jobs: google-chrome --version npm ci + - name: Authenticate to Google Cloud with OIDC + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 + with: + workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }} + # SCHWAB_PROXY_URL retired on 2026-11-02; keep the secret, but do not read it. - name: Run Refresh Script run: npm start env: - GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} SCHWAB_USERNAME: ${{ secrets.SCHWAB_USERNAME }} SCHWAB_PASSWORD: ${{ secrets.SCHWAB_PASSWORD }} SCHWAB_TOTP_SECRET: ${{ secrets.SCHWAB_TOTP_SECRET }} diff --git a/README.md b/README.md index 8e11c7c..f1b99d0 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,17 @@ node tests/test_auth_code_extraction.js | `SCHWAB_REDIRECT_URI` | Yes | none | OAuth redirect URI registered with the application. | | `GCP_PROJECT_ID` | Yes | none | Google Cloud project that holds the secret. | | `GCP_SECRET_ID` | Yes | none | Secret Manager secret to update. | -| `GCP_SA_KEY` | No | ambient credentials | Service account key JSON when ambient credentials are not available. | +| `GCP_SA_KEY` | No | ambient credentials | Legacy option for local callers using a service account key. | | `GCP_SECRET_VERSION_RETENTION` | No | `1` | Number of secret versions to keep; older versions are permanently destroyed and cannot be restored. | | `TELEGRAM_TOKEN` | For workflow alerts | `TG_TOKEN` | Unified Telegram bot token (QSL 资产管家 bot). | | `GLOBAL_TELEGRAM_CHAT_ID` | For workflow alerts | none | Alert chat IDs, separated by commas, semicolons, or whitespace. | +Set the GitHub variables `GCP_WORKLOAD_IDENTITY_PROVIDER` and +`GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT` for the scheduled workflow. Configure +the provider to accept this repository's default-branch refresh workflow. +Grant the refresh identity only the Secret Manager version permissions used by +the workflow, on the configured destination secret. + The workflow makes one direct refresh attempt. On failure it sends a Telegram alert with the workflow run link, then leaves the job failed. Alert delivery errors or missing bot configuration also fail the alert step. No refresh errors, diff --git a/README.zh-CN.md b/README.zh-CN.md index ccef2a7..70978d8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -50,11 +50,13 @@ node tests/test_auth_code_extraction.js | `SCHWAB_REDIRECT_URI` | 是 | 无 | 在应用中登记的 OAuth 回调地址。 | | `GCP_PROJECT_ID` | 是 | 无 | 存放密钥的 Google Cloud 项目。 | | `GCP_SECRET_ID` | 是 | 无 | 要更新的 Secret Manager 密钥。 | -| `GCP_SA_KEY` | 否 | 环境默认凭据 | 无法使用环境默认凭据时提供的 service account key JSON。 | +| `GCP_SA_KEY` | 否 | 环境默认凭据 | 仍使用服务账号密钥的本地调用者可用。 | | `GCP_SECRET_VERSION_RETENTION` | 否 | `1` | 保留的密钥版本数量;更早的版本会被永久销毁,无法恢复。 | | `TELEGRAM_TOKEN` | 工作流告警需要 | `TG_TOKEN` | 统一 Telegram bot(QSL 资产管家 bot)的 token。 | | `GLOBAL_TELEGRAM_CHAT_ID` | 工作流告警需要 | 无 | 告警 chat ID,多个 ID 用逗号、分号或空白分隔。 | +定时工作流使用 GitHub OIDC。设置 GitHub 变量 `GCP_WORKLOAD_IDENTITY_PROVIDER` 和 `GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT`,让 Google 只信任本仓库默认分支的刷新工作流。把刷新用到的 Secret Manager 版本操作权限限制到目标密钥。 + 工作流只进行一次直连刷新。失败时发送带工作流链接的 Telegram 告警,并保留任务失败状态。告警发送失败或缺少 bot 配置时,告警步骤也会失败。消息不包含刷新错误详情、凭据或 token 响应。 为统一 bot 配置 GitHub Actions secrets:`TELEGRAM_TOKEN`(或已有的 `TG_TOKEN`)和 `GLOBAL_TELEGRAM_CHAT_ID`。测试使用模拟请求,不发送真实告警。 diff --git a/tests/test_workflow_config_sources.sh b/tests/test_workflow_config_sources.sh index 68ed640..3fe4207 100644 --- a/tests/test_workflow_config_sources.sh +++ b/tests/test_workflow_config_sources.sh @@ -7,7 +7,14 @@ workflow_file="$repo_dir/.github/workflows/main.yml" grep -Fq 'GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}' "$workflow_file" grep -Fq 'GCP_SECRET_ID: ${{ vars.GCP_SECRET_ID }}' "$workflow_file" grep -Fq 'SCHWAB_REDIRECT_URI: ${{ vars.SCHWAB_REDIRECT_URI }}' "$workflow_file" -grep -Fq 'GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}' "$workflow_file" +grep -Fq 'id-token: write' "$workflow_file" +grep -Fq 'workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }}' "$workflow_file" +grep -Fq 'service_account: ${{ vars.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }}' "$workflow_file" +grep -Fq 'github.ref_name == github.event.repository.default_branch' "$workflow_file" +if grep -Fq 'GCP_SA_KEY' "$workflow_file"; then + echo "workflow must use federated Google credentials" >&2 + exit 1 +fi grep -Fq 'TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN || secrets.TG_TOKEN }}' "$workflow_file" grep -Fq 'GLOBAL_TELEGRAM_CHAT_ID: ${{ secrets.GLOBAL_TELEGRAM_CHAT_ID }}' "$workflow_file" grep -Fq 'run: node scripts/notify_refresh_failure.js' "$workflow_file"