diff --git a/.github/workflows/prep-self-release.yml b/.github/workflows/prep-self-release.yml index a3654290..b59e97a3 100644 --- a/.github/workflows/prep-self-release.yml +++ b/.github/workflows/prep-self-release.yml @@ -26,6 +26,8 @@ on: jobs: prep_release: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -37,7 +39,7 @@ jobs: id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} post_version_spec: ${{ github.event.inputs.post_version_spec }} target: jupyter-server/jupyter_releaser diff --git a/.github/workflows/publish-changelog.yml b/.github/workflows/publish-changelog.yml index dc31f350..8ec872ad 100644 --- a/.github/workflows/publish-changelog.yml +++ b/.github/workflows/publish-changelog.yml @@ -12,18 +12,27 @@ on: jobs: publish_changelog: runs-on: ubuntu-latest + environment: release steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install Dependencies shell: bash run: | pip install -e . + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Publish changelog id: publish-changelog uses: jupyter-server/jupyter_releaser/.github/actions/publish-changelog@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} diff --git a/.github/workflows/publish-self-release.yml b/.github/workflows/publish-self-release.yml index d039a844..ad9f9beb 100644 --- a/.github/workflows/publish-self-release.yml +++ b/.github/workflows/publish-self-release.yml @@ -17,8 +17,6 @@ jobs: runs-on: ubuntu-latest environment: release permissions: - # This is useful if you want to use PyPI trusted publisher - # and NPM provenance id-token: write steps: - uses: actions/checkout@v4 @@ -27,11 +25,16 @@ jobs: shell: bash run: | pip install -e . + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Populate Release id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} target: jupyter-server/jupyter_releaser branch: ${{ github.event.inputs.branch }} release_url: ${{ github.event.inputs.release_url }} @@ -43,7 +46,7 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} target: ${{ github.event.inputs.target }} release_url: ${{ steps.populate-release.outputs.release_url }} diff --git a/docs/source/background/theory.md b/docs/source/background/theory.md index c6fe3647..dbe796ff 100644 --- a/docs/source/background/theory.md +++ b/docs/source/background/theory.md @@ -13,6 +13,13 @@ This project should help maintainers reduce toil and save time in the release pr - Dry run publish on CI - Revert to Dev version after release (optional) +## Security + +We strive to use the most secure release practices possible, reflected in the `Checklist for Adoption` +and the example workflows. +This includes using PyPI Trusted Publishing, using GitHub Environments, encouraging the use of Rulesets and GitHub Apps with limited bypass capability, and provenance data for npm. +In addition, there is an automatic check for whether the user who triggered the action is an admin. + ## Action Details Detailed workflows are available to draft a changelog, draft a release, publish a release, and check a release. diff --git a/docs/source/how_to_guides/convert_repo_from_repo.md b/docs/source/how_to_guides/convert_repo_from_repo.md index af0fb0e9..462cecb8 100644 --- a/docs/source/how_to_guides/convert_repo_from_repo.md +++ b/docs/source/how_to_guides/convert_repo_from_repo.md @@ -16,26 +16,32 @@ See checklist below for details: ## Checklist for Adoption -- [ ] Add a GitHub [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), preferably from a "machine user" GitHub - account that has admin access to the repository. The token itself will - need "public_repo", and "repo:status" permissions. Save the token as - `ADMIN_GITHUB_TOKEN` - in the [repository secrets](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository). We need this - access token to allow for branch protection rules, which block the pushing - of commits when using the `GITHUB_TOKEN`, even when run from an admin user - account. +- [ ] Set up a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps#github-apps-that-act-on-their-own-behalf) on your organization (or personal account for a personal project). + + - Disable the web hook + - Enable Repository permissions > Contents > Read and write + - Select "Only on this account" + - Click "Create GitHub App" + - Browse to the App Settings + - Select "Install App" and install on all repositories + - Under "General" click "Generate a private key" + - Store the `APP_ID` and the private key in a secure location (Jupyter Vault if using a Jupyter Org) + +- [ ] Create a "release" [environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) on your repository and add an `APP_ID` Environment Variable and `APP_PRIVATE_KEY` secret. + The environment should be enabled for ["Protected branches only"](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-branches-and-tags). + +- [ ] Configure [Rulesets](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets) for the repository + + - Set up branch protection (with default rules) on publication branches + - Remove global tag protection. + - Add a branch Ruleset for all branches + - Allow the GitHub App to bypass protections + - Set up Pull Request and Required Checks + - Add a tags Ruleset for all tags + - Allow the GitHub App to bypass protections - [ ] Set up PyPI: -
Using PyPI token (legacy way) - -- Add access token for the [PyPI registry](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#saving-credentials-on-github) stored as `PYPI_TOKEN`. - _Note_ For security reasons, it is recommended that you scope the access - to a single repository. Additionally, this token should belong to a - machine account and not a user account. - -
-
Using PyPI trusted publisher (modern way) - Set up your PyPI project by [adding a trusted publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) @@ -45,10 +51,18 @@ See checklist below for details:
+
Using PyPI token (legacy way) + +- Add access token for the [PyPI registry](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#saving-credentials-on-github) stored as `PYPI_TOKEN`. + _Note_ For security reasons, it is recommended that you scope the access + to a single repository. Additionally, this token should belong to a + machine account and not a user account. + +
+ - [ ] If needed, add access token for [npm](https://docs.npmjs.com/creating-and-viewing-access-tokens), saved as `NPM_TOKEN`. Again this should be created using a machine account that only has publish access. -- [ ] Ensure that only trusted users with 2FA have admin access to the - repository, since they will be able to trigger releases. +- [ ] Ensure that only trusted users with 2FA have admin access to the repository, since they will be able to trigger releases. - [ ] Switch to Markdown Changelog - We recommend [MyST](https://myst-parser.readthedocs.io/en/latest/?badge=latest), especially if some of your docs are in reStructuredText. - Can use `pandoc -s changelog.rst -o changelog.md` and some hand edits as needed. diff --git a/docs/source/how_to_guides/maintain_fork.md b/docs/source/how_to_guides/maintain_fork.md index fe079c5d..da624882 100644 --- a/docs/source/how_to_guides/maintain_fork.md +++ b/docs/source/how_to_guides/maintain_fork.md @@ -2,7 +2,7 @@ ## How to keep fork of Jupyter Releaser up to date -- The manual workflow files target the `@v1` actions in the source repository, which means that as long as +- The manual workflow files target the `@v2` actions in the source repository, which means that as long as the workflow files themselves are up to date, you will always be running the most up to date actions. - Make sure your workflow is up to date by checking the "Fetch Upstream" dropdown on the main page of your fork. diff --git a/example-workflows/full-release.yml b/example-workflows/full-release.yml index 186631b8..b8ee1fcf 100644 --- a/example-workflows/full-release.yml +++ b/example-workflows/full-release.yml @@ -27,12 +27,10 @@ on: description: "Comma separated list of steps to skip during Populate Release" required: false jobs: - full_release: + prep_release: runs-on: ubuntu-latest permissions: - # This is useful if you want to use PyPI trusted publisher - # and NPM provenance - id-token: write + contents: write steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -40,34 +38,48 @@ jobs: id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} + # silent: ${{ github.event.inputs.silent }} post_version_spec: ${{ github.event.inputs.post_version_spec }} + target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} - # silent: ${{ github.event.inputs.silent }} since: ${{ github.event.inputs.since }} since_last_stable: ${{ github.event.inputs.since_last_stable }} + publish_release: + needs: [prep_release] + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Populate Release id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} + target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} - release_url: ${{ steps.prep-release.outputs.release_url }} + release_url: ${{ github.event.inputs.release_url }} steps_to_skip: ${{ github.event.inputs.steps_to_skip }} - name: Finalize Release id: finalize-release env: - # The following are needed if you use legacy PyPI set up - # PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - # PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} - # TWINE_USERNAME: __token__ NPM_TOKEN: ${{ secrets.NPM_TOKEN }} uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} + target: ${{ github.event.inputs.target }} release_url: ${{ steps.populate-release.outputs.release_url }} - name: "** Next Step **" @@ -75,3 +87,9 @@ jobs: run: | echo "Verify the final release" echo ${{ steps.finalize-release.outputs.release_url }} + + - name: "** Failure Message **" + if: ${{ failure() }} + run: | + echo "Failed to Publish the Draft Release Url:" + echo ${{ steps.populate-release.outputs.release_url }} diff --git a/example-workflows/prep-release.yml b/example-workflows/prep-release.yml index 83f876f8..4f8621f1 100644 --- a/example-workflows/prep-release.yml +++ b/example-workflows/prep-release.yml @@ -26,6 +26,8 @@ on: jobs: prep_release: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -33,11 +35,12 @@ jobs: id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} + # silent: ${{ github.event.inputs.silent }} post_version_spec: ${{ github.event.inputs.post_version_spec }} + target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} - # silent: ${{ github.event.inputs.silent }} since: ${{ github.event.inputs.since }} since_last_stable: ${{ github.event.inputs.since_last_stable }} diff --git a/example-workflows/publish-changelog.yml b/example-workflows/publish-changelog.yml index ad612f26..60af4c5f 100644 --- a/example-workflows/publish-changelog.yml +++ b/example-workflows/publish-changelog.yml @@ -12,13 +12,21 @@ on: jobs: publish_changelog: runs-on: ubuntu-latest + environment: release steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Publish changelog id: publish-changelog uses: jupyter-server/jupyter_releaser/.github/actions/publish-changelog@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} branch: ${{ github.event.inputs.branch }} - name: "** Next Step **" diff --git a/example-workflows/publish-release.yml b/example-workflows/publish-release.yml index cf6d9058..c1881060 100644 --- a/example-workflows/publish-release.yml +++ b/example-workflows/publish-release.yml @@ -15,18 +15,23 @@ on: jobs: publish_release: runs-on: ubuntu-latest + environment: release permissions: - # This is useful if you want to use PyPI trusted publisher - # and NPM provenance id-token: write steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Populate Release id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} branch: ${{ github.event.inputs.branch }} release_url: ${{ github.event.inputs.release_url }} steps_to_skip: ${{ github.event.inputs.steps_to_skip }} @@ -34,14 +39,10 @@ jobs: - name: Finalize Release id: finalize-release env: - # The following are needed if you use legacy PyPI set up - # PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - # PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} - # TWINE_USERNAME: __token__ NPM_TOKEN: ${{ secrets.NPM_TOKEN }} uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} release_url: ${{ steps.populate-release.outputs.release_url }} - name: "** Next Step **"