diff --git a/.github/workflows/apply.yml b/.github/workflows/apply.yml index 8de5da2..39b5803 100644 --- a/.github/workflows/apply.yml +++ b/.github/workflows/apply.yml @@ -32,6 +32,7 @@ jobs: - name: Find sha for plan id: sha env: + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', matrix.workspace)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} @@ -55,6 +56,7 @@ jobs: TF_WORKSPACE: ${{ matrix.workspace }} AWS_ACCESS_KEY_ID: ${{ secrets.RW_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RW_AWS_SECRET_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', matrix.workspace)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml index 00bd852..38b9e46 100644 --- a/.github/workflows/clean.yml +++ b/.github/workflows/clean.yml @@ -60,6 +60,7 @@ jobs: TF_WORKSPACE_OPT: ${{ matrix.workspace }} AWS_ACCESS_KEY_ID: ${{ secrets.RW_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RW_AWS_SECRET_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', matrix.workspace)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 88e0e75..b7e1089 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -31,6 +31,7 @@ jobs: name: Clean Up runs-on: ubuntu-latest env: + GITHUB_TOKEN: ${{ secrets.RO_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RO_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RO_GITHUB_APP_INSTALLATION_ID_{0}', github.repository_owner)] || secrets.RO_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RO_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/fix.yml b/.github/workflows/fix.yml index dd414c8..21352a4 100644 --- a/.github/workflows/fix.yml +++ b/.github/workflows/fix.yml @@ -135,6 +135,9 @@ jobs: steps: - name: Generate app token id: token + env: + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} + if: ${{ ! env.GITHUB_TOKEN }} uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 with: app_id: ${{ secrets.RW_GITHUB_APP_ID }} @@ -146,7 +149,7 @@ jobs: with: repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.event.pull_request.head.sha || github.sha }} - token: ${{ steps.token.outputs.token }} + token: ${{ secrets.RW_GITHUB_TOKEN || steps.token.outputs.token }} path: head - name: Checkout uses: actions/checkout@v4 @@ -184,7 +187,7 @@ jobs: - if: steps.github-modified.outputs.this == 'true' && github.event_name != 'pull_request_target' uses: ./base/.github/actions/git-push env: - GITHUB_TOKEN: ${{ steps.token.outputs.token }} + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN || steps.token.outputs.token }} with: suffix: fix working-directory: head diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index eb17c7e..ae7deb9 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,6 +29,7 @@ jobs: name: Sync runs-on: ubuntu-latest env: + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', github.repository_owner)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/plan.yml b/.github/workflows/plan.yml index 1442cb8..15c6ad8 100644 --- a/.github/workflows/plan.yml +++ b/.github/workflows/plan.yml @@ -64,6 +64,7 @@ jobs: TF_WORKSPACE: ${{ matrix.workspace }} AWS_ACCESS_KEY_ID: ${{ secrets.RO_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RO_AWS_SECRET_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.RO_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RO_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RO_GITHUB_APP_INSTALLATION_ID_{0}', matrix.workspace)] || secrets.RO_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RO_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index ca0af0f..568c2c1 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -54,6 +54,7 @@ jobs: TF_WORKSPACE_OPT: ${{ matrix.workspace }} AWS_ACCESS_KEY_ID: ${{ secrets.RW_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RW_AWS_SECRET_ACCESS_KEY }} + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', matrix.workspace)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} @@ -107,6 +108,9 @@ jobs: steps: - name: Generate app token id: token + env: + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} + if: ${{ ! env.GITHUB_TOKEN }} uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 with: app_id: ${{ secrets.RW_GITHUB_APP_ID }} @@ -116,7 +120,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - token: ${{ steps.token.outputs.token }} + token: ${{ secrets.RW_GITHUB_TOKEN || steps.token.outputs.token }} - uses: ./.github/actions/git-config-user - env: WORKSPACES: ${{ needs.prepare.outputs.workspaces }} diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 9ff4cd7..d7a6cb7 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -25,6 +25,7 @@ jobs: working-directory: scripts - name: Update PRs env: + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', matrix.workspace)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade.yml index 6f79579..3730bf0 100644 --- a/.github/workflows/upgrade.yml +++ b/.github/workflows/upgrade.yml @@ -14,6 +14,7 @@ jobs: with: ref: inputs.ref secrets: + RW_GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} GITHUB_APP_ID: ${{ secrets.RW_GITHUB_APP_ID }} GITHUB_APP_INSTALLATION_ID: ${{ secrets[format('RW_GITHUB_APP_INSTALLATION_ID_{0}', github.repository_owner)] || secrets.RW_GITHUB_APP_INSTALLATION_ID }} GITHUB_APP_PEM_FILE: ${{ secrets.RW_GITHUB_APP_PEM_FILE }} diff --git a/.github/workflows/upgrade_reusable.yml b/.github/workflows/upgrade_reusable.yml index 80a1cc1..1d6cf0a 100644 --- a/.github/workflows/upgrade_reusable.yml +++ b/.github/workflows/upgrade_reusable.yml @@ -9,12 +9,14 @@ on: description: The github-mgmt-template ref to upgrade to default: master secrets: + RW_GITHUB_TOKEN: + required: false GITHUB_APP_ID: - required: true + required: false GITHUB_APP_INSTALLATION_ID: - required: true + required: false GITHUB_APP_PEM_FILE: - required: true + required: false jobs: upgrade: @@ -26,6 +28,9 @@ jobs: steps: - name: Generate app token id: token + env: + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN }} + if: ${{ ! env.GITHUB_TOKEN }} uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 with: app_id: ${{ secrets.GITHUB_APP_ID }} @@ -42,7 +47,7 @@ jobs: uses: actions/checkout@v4 with: path: github-mgmt - token: ${{ steps.token.outputs.token }} + token: ${{ secrets.RW_GITHUB_TOKEN || steps.token.outputs.token }} - name: Copy files from the template run: | for file in $(git ls-files ':!:github/*.yml' ':!:scripts/src/actions/fix-yaml-config.ts' ':!:terraform/*_override.tf' ':!:.github/workflows/*_reusable.yml' ':!:README.md'); do @@ -57,7 +62,7 @@ jobs: working-directory: github-mgmt - uses: ./github-mgmt-template/.github/actions/git-push env: - GITHUB_TOKEN: ${{ steps.token.outputs.token }} + GITHUB_TOKEN: ${{ secrets.RW_GITHUB_TOKEN || steps.token.outputs.token }} with: suffix: upgrade working-directory: github-mgmt diff --git a/docs/SETUP.md b/docs/SETUP.md index b4154a1..d176112 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -87,7 +87,15 @@ - [ ] one with read & write policy attached - [ ] Modify [terraform/terraform_override.tf](terraform/terraform_override.tf) to reflect your AWS setup -## GitHub App +## GitHub API access + +There are two possible ways for GitHub API access: +- With GitHub Apps, which has the benefit of not being tied to a GitHub user +- [experimental] With personal access tokens for a GitHub user, which has the benefit of more granular permissions, but is limited in functionality and requires more manual work: + - Only teams and team memberships are supported right now + - The GitHub user must be a team maintainer for any teams it should manage + +### GitHub App *NOTE*: If you already have a GitHub App with required permissions you can skip the app creation step. @@ -114,18 +122,50 @@ - [ ] [Install the GitHub Apps](https://docs.github.com/en/developers/apps/managing-github-apps/installing-github-apps) in the GitHub organization for `All repositories` +### Personal access token + +- [ ] Create a separate dedicated GitHub account for GitHub Management. It is not recommended to use your personal account. +- [ ] [Create two fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) for the dedicated GitHub account - *they are going to be used by terraform and GitHub Actions to authenticate with GitHub*: + - Resource owner: The GitHub Organization + - Expiration: 366 days (you can also [remove the limit](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization#enforcing-a-maximum-lifetime-policy-for-personal-access-tokens) + - Repository access: + - Only select repositories: Select the GitHub Management repository + - Permissions: +
read-only + + - `Repository permissions` + - `Contents`: `Read-only` + - `Metadata`: `Read-only` + - `Organization permissions` + - `Members`: `Read-only` +
+
read & write + + - `Repository permissions` + - `Contents`: `Read & Write` + - `Metadata`: `Read-only` + - `Organization permissions` + - `Members`: `Read & Write` +
+- [ ] Switch to an organization owner account and approve the tokens in the organizations settings, under "Personal access tokens > Pending requests" +- [ ] Give the dedicated GitHub account write access to the GitHub Management Repository + ## GitHub Repository Secrets - [ ] [Create encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-an-organization) for the GitHub organization and allow the repository to access them (\*replace `$GITHUB_ORGANIZATION_NAME` with the GitHub organization name) - *these secrets are read by the GitHub Action workflows* - - [ ] Go to `https://github.com/organizations/$GITHUB_ORGANIZATION_NAME/settings/apps/$GITHUB_APP_NAME` and copy the `App ID` - - [ ] `RO_GITHUB_APP_ID` - - [ ] `RW_GITHUB_APP_ID` - - [ ] Go to `https://github.com/organizations/$GITHUB_ORGANIZATION_NAME/settings/installations`, click `Configure` next to the `$GITHUB_APP_NAME` and copy the numeric suffix from the URL - - [ ] `RO_GITHUB_APP_INSTALLATION_ID` (or `RO_GITHUB_APP_INSTALLATION_ID_$GITHUB_ORGANIZATION_NAME` for organizations other than the repository owner) - - [ ] `RW_GITHUB_APP_INSTALLATION_ID` (or `RW_GITHUB_APP_INSTALLATION_ID_$GITHUB_ORGANIZATION_NAME` for organizations other than the repository owner) - - [ ] Go to `https://github.com/organizations/$GITHUB_ORGANIZATION_NAME/settings/apps/$GITHUB_APP_NAME`, click `Generate a private key` and copy the contents of the downloaded PEM file - - [ ] `RO_GITHUB_APP_PEM_FILE` - - [ ] `RW_GITHUB_APP_PEM_FILE` + - If you use a GitHub App: + - [ ] Go to `https://github.com/organizations/$GITHUB_ORGANIZATION_NAME/settings/apps/$GITHUB_APP_NAME` and copy the `App ID` + - [ ] `RO_GITHUB_APP_ID` + - [ ] `RW_GITHUB_APP_ID` + - [ ] Go to `https://github.com/organizations/$GITHUB_ORGANIZATION_NAME/settings/installations`, click `Configure` next to the `$GITHUB_APP_NAME` and copy the numeric suffix from the URL + - [ ] `RO_GITHUB_APP_INSTALLATION_ID` (or `RO_GITHUB_APP_INSTALLATION_ID_$GITHUB_ORGANIZATION_NAME` for organizations other than the repository owner) + - [ ] `RW_GITHUB_APP_INSTALLATION_ID` (or `RW_GITHUB_APP_INSTALLATION_ID_$GITHUB_ORGANIZATION_NAME` for organizations other than the repository owner) + - [ ] Go to `https://github.com/organizations/$GITHUB_ORGANIZATION_NAME/settings/apps/$GITHUB_APP_NAME`, click `Generate a private key` and copy the contents of the downloaded PEM file + - [ ] `RO_GITHUB_APP_PEM_FILE` + - [ ] `RW_GITHUB_APP_PEM_FILE` + - If you use personal access tokens + - [ ] `RO_GITHUB_TOKEN` + - [ ] `RW_GITHUB_TOKEN` - [ ] Use the values generated during [AWS](#aws) setup - [ ] `RO_AWS_ACCESS_KEY_ID` - [ ] `RW_AWS_ACCESS_KEY_ID` diff --git a/scripts/src/env.ts b/scripts/src/env.ts index e0f3bbb..6e0b670 100644 --- a/scripts/src/env.ts +++ b/scripts/src/env.ts @@ -4,6 +4,7 @@ export default { TF_WORKING_DIR: process.env.TF_WORKING_DIR || '../terraform', FILES_DIR: process.env.FILES_DIR || '../files', GITHUB_DIR: process.env.GITHUB_DIR || '../github', + GITHUB_TOKEN: process.env.GITHUB_TOKEN || '', GITHUB_APP_ID: process.env.GITHUB_APP_ID || '', GITHUB_APP_INSTALLATION_ID: process.env.GITHUB_APP_INSTALLATION_ID || '', GITHUB_APP_PEM_FILE: process.env.GITHUB_APP_PEM_FILE || '', diff --git a/scripts/src/github.ts b/scripts/src/github.ts index 8473e71..b8e0828 100644 --- a/scripts/src/github.ts +++ b/scripts/src/github.ts @@ -102,16 +102,21 @@ export class GitHub { // NOTE: We import these dynamically so that they can be mocked const {createAppAuth} = await import('@octokit/auth-app') const {Octokit} = await import('@octokit/rest') - const auth = createAppAuth({ - appId: env.GITHUB_APP_ID, - privateKey: env.GITHUB_APP_PEM_FILE - }) - const installationAuth = await auth({ - type: 'installation', - installationId: env.GITHUB_APP_INSTALLATION_ID - }) + let token = env.GITHUB_TOKEN; + if (token == '') { + const auth = createAppAuth({ + appId: env.GITHUB_APP_ID, + privateKey: env.GITHUB_APP_PEM_FILE + }) + const installationAuth = await auth({ + type: 'installation', + installationId: env.GITHUB_APP_INSTALLATION_ID + }) + token = installationAuth.token; + } + const client = new (Octokit.plugin(retry, throttling))({ - auth: installationAuth.token, + auth: token, throttle: { onRateLimit: ( retryAfter: number,