mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.17k stars 32.08k forks source link

Automate releases: publish from CI + npm provenance #38321

Open gabibguti opened 1 year ago

gabibguti commented 1 year ago

Summary 💡

Hi! I've noticed you have a very well-defined release process with scripts to support this process for mui/material-ui. It looks to me like the scripts are currently run manually, and we could automate the script runs, that don't require human action, using GitHub workflows to release.

Examples 🌈

Examples of GitHub workflows releasing to npm: https://github.com/reduxjs/react-redux/blob/4a7e129ee537d35c53da258ccf7924a0376391ee/.github/workflows/publish.yaml https://github.com/SAP/ui5-webcomponents/blob/3b7c711875303be0bb995c80545a9786b2ec511c/.github/workflows/release-custom-tag.yaml

Motivation 🔦

Automating the release makes the process easier for you (fewer things to do each time!). It also improves the security of the release by using a "trusted builder". A "trusted builder" provides a higher level of confidence, for example, that your checking out the correct commit and that yarn package was not compromised, over running in a local machine.

Additional context

If you are willing to automate your releasing script runs using GitHub workflows, I can support you by opening PRs!

I'm Gabriela and I work on behalf of Google and the OpenSSF suggesting supply-chain security changes :)

oliviertassinari commented 1 year ago

We started to explore this problem in https://mui-org.notion.site/Release-npm-packages-from-CI-09d7e44afc464b82ac3351504988f124.

Janpot commented 1 year ago

As @oliviertassinari mentioned, automating releases is on our radar. At the moment we're not working on a concrete implementation, but your input would be very valuable. Could you sketch out in a bit more detail which changes you propose to make?

gabibguti commented 1 year ago

Ah, very nice that you were already tracking this! Here's what I was thinking as proposal:

Keep the "Prerequisites" and "Prepare" parts manual, because they are mostly about making sure we have everything we need to release and require human action.

The "Release" part, the one that tags, builds and publishes the release, is the one that I would automate. From a supply-chain security point of view, it's imporatant to guarantee that the build is isolated when releasing. In other words, that during the build, it does not have external (undesired) interference.

We can use GitHub workflows or CircleCI to automate the release process. I recommend using GitHub workflows due to CircleCI security problems such as the token security breach this year. Another advantage of using GitHub workflows is that it enables publishing to npm with provenance. With the release workflow setup, we can trigger it by tagging the commit to be released. This will ensure that the build and publish steps are only triggered when the release is ready and checking out to the correct commit.

Additionally, we can also automate the "Documentation" part in the same workflow.

However, one disadvantage of automating this process, regardless of using GitHub workflows or CircleCI, is that we would need to store a long-lived npm publishing token in one of these platforms. This is because npm has no native OIDC. What we can do to mitigate this risk is rotating the npm token on every release manually as part of the release process.

gabibguti commented 1 year ago
# Prerequisites
# - Set npm publishing token on NPM_TOKEN secret on GitHub UI
# Release
# - Tag the commit to release through GitHub UI or using your script
# - Wait for release workflow to run

# Release workflow draft

on:
  push:
    tags:
      - v*

permissions:
  contents: read

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18.x'
          registry-url: 'https://registry.npmjs.org/'
      - run: |
          yarn
          yarn release:build
          yarn release:publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
          NPM_CONFIG_PROVENANCE: true
oliviertassinari commented 1 year ago
on:
  push:
    tags:
      - v*

I'm not sure about the auto trigger on tag push. I have seen cases where we push rogue tags. a workflow_dispatch feels safer to get started (available to contributors with write permission, which would only work if there is actually no duplicated version to publish). It's what the two examples provided uses.

is that we would need to store a long-lived npm publishing token in one of these platforms.

Even if the token is only changed once a year, this still sounds safer thanks to the provenance feature. I leaking npm token used to release wouldn't have its provenance certified.

gabibguti commented 1 year ago

Alright! Sure, we could trigger the workflow manually with workflow_dispatch, we would just need to provide a tag or commit as input.

As you said, only contributors with write permissions are able to run workflows in the repository. My understand is the same from this discussion.

About not publishing duplicate versions, my understanding is that GitHub and npm would automatically avoid it. Releasing to GitHub with actions/github-script, it does not allow you to publish a duplicate release with the same tag throwing an HTTP 422 error, and releasing to npm, it does not allow you to publish a duplicate version throwing an HTTP 403 error. Therefore, we don't need a validation here since the workflow will fail to publish a duplicate release.

An additional info here, we can also trigger on tag push but make sure the workflow is "approved" before really running using GitHub Environments.

Even if the token is only changed once a year, this still sounds safer thanks to the provenance feature. I leaking npm token used to release wouldn't have its provenance certified.

Yeah, agreed. That makes sense to me.

Janpot commented 1 year ago

Alright! Sure, we could trigger the workflow manually with workflow_dispatch, we would just need to provide a tag or commit as input.

We could consider triggering it in a script through the GitHub API, then the publishing steps can remain the same. I believe it should be possible to automatically rotate the npm token in the same script?

Even if the token is only changed once a year, this still sounds safer thanks to the provenance feature. I leaking npm token used to release wouldn't have its provenance certified.

Would this block the package from being published? I mean I don't think I've ever verified whether a package has provenance.

gabibguti commented 1 year ago

We could consider triggering it in a script through the GitHub API, then the publishing steps can remain the same. I believe it should be possible to automatically rotate the npm token in the same script?

Sort of. I think we can't run npm login or npm token create and get the token response automatically in a script since it interacts with the user for password and OTP confirmation. But doing that manually we can run a script like:

gh secret set NPM_PUBLISH_TOKEN --body "$TOKEN"
gh workflow run release.yml -f tag=v1.0.0
gh secret delete NPM_PUBLISH_TOKEN
npm token revoke
Janpot commented 1 year ago

Sort of. I think we can't run npm login or npm token create and get the token response automatically in a script since it interacts with the user for password and OTP confirmation.

👍 I think it's ok for the script to prompt for npm login and a OTP confirmation.

gabibguti commented 1 year ago

Should I start a PR draft?

Including an .mjs script to rotate the npm token and trigger the release workflow, plus a release workflow using GitHub workflows triggered by workflow_dispatch.

gabibguti commented 1 year ago

And regarding your other question:

Would this block the package from being published? I mean I don't think I've ever verified whether a package has provenance.

No, it wouldn't. Provenance becomes impactful once both the maintainers and the consumers can publish it and verify it. The purpose of provenance is to be able to audit where, when and how the release was produced. Giving maintainers and consumers more confidence that the release was published from the original repository by its original authors. Over time, when your package has a history of provenance and then one release does not have provenance, it can also help indicate if the version was published by a malicious actor. Consumers can already verify provenance on the package page, for example on js-cookie, or with npm audit signatures, but if you start publishing with provenance now, it can become even more impactful once npm adds more features such as only installing package XYZ if it has provenance.

Janpot commented 1 year ago

Even if the token is only changed once a year, this still sounds safer thanks to the provenance feature. I leaking npm token used to release wouldn't have its provenance certified.

I disagree with the idea that it's safer. If the provenance can't be enforced on the publisher level then I don't see how it's more secure than the 2FA we have now. However, in my opinion this is outweighed by the benefits of automation, mostly in terms of producing clean and reproducible builds. So let's go for it!

Should I start a PR draft?

👍

also came across this project. We shouldn't use it, but interesting nonetheless 🙂.