microsoft / tslib

Runtime library for TypeScript helpers.
BSD Zero Clause License
1.23k stars 123 forks source link

Generate SLSA Build L3 provenance #210

Open ianlewis opened 1 year ago

ianlewis commented 1 year ago

Hi 👋

I'm Ian, working on behalf of Google and the Open Source Security Foundation (OpenSSF) to help open source projects to improve their supply chain security.

After some analysis tslib showed up as one of the top transitive dependencies in the JavaScript/Node ecosystem.

I would like to offer help update tslib builds to generate a SLSA Build Level 3 Provenance. The Supply chain Levels for Software Artifacts, or SLSA (salsa) framework aims to improve security in the build process by defining increasing levels of build integrity.

Now that npm supports distributing package provenance via the official npm registry, I think this would be a good time introduce provenance generation to tslib.

Given tslib is such a highly used package, I suggest generating SLSA Build L3 provenance using the Node.js Builder reusable workflow for GitHub Actions. This would provide the highest level of integrity for the build while hopefully remaining a minimal change to the existing publish.yml workflow.

Would the tslib project be open to a PR introducing these changes?

RyanCavanaugh commented 1 year ago

@rbuckton @weswigham thoughts?

rbuckton commented 1 year ago

This may be valuable, although tslib itself has no actual build outputs. Our publish process only performs what is essentially a lint against modules/index.js to make sure it has the same exports as tslib.js, though we do run some additional tests during CI.

SLSA only validates the provenance of a single package and doesn't validate transitive dependencies. If tslib doesn't support SLSA it wouldn't necessarily impact projects that depend on it that want to support SLSA themselves.

I am currently reading up on SLSA, but it would help to know what this change would entail as far as ongoing maintenance (i.e., signing certificate renewal, PR review requirements, etc.).

weswigham commented 1 year ago

I've read a lot of the docs, and, for us, the ask is basically just to change our build & publish actions to their "verified" actions that do a bunch of hashing and publish integrity metadata alongside the publish. Technically, we can get L1 just by passing --provenance to our existing npm publish call. The L3-level @ianlewis mentions is stronger integrity guarantees - that the build wasn't tampered with, rather than just providing a link between git hash and build artifact.

Which reading the docs linked, implies changing our publish yaml to (guesstimating a bit, since the parts are, well, in parts, and we want to retain our existing on-github-release behavior, not on-git-tag):

name: Publish to NPM

on:
  release:
    types: [created]

jobs:
  build:
    permissions:
      id-token: write # For signing
      contents: read # For repo checkout.
      actions: read # For getting workflow run info.
    uses: slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml@v1.7.0
    with:
      run-scripts: "i, test"
  publish:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - name: Set up Node registry authentication
        uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
        with:
          node-version: 18
          registry-url: "https://registry.npmjs.org"

      - name: publish
        id: publish
        uses: slsa-framework/slsa-github-generator/actions/nodejs/publish@4314fec3d06bb217f163b89466dcd34be65b9bf1 # v1.6.0
        with:
          access: public
          node-auth-token: ${{ secrets.NPM_TOKEN }}
          package-name: ${{ needs.build.outputs.package-name }}
          package-download-name: ${{ needs.build.outputs.package-download-name }}
          package-download-sha256: ${{ needs.build.outputs.package-download-sha256 }}
          provenance-name: ${{ needs.build.outputs.provenance-name }}
          provenance-download-name: ${{ needs.build.outputs.provenance-download-name }}
          provenance-download-sha256: ${{ needs.build.outputs.provenance-download-sha256 }}

and adding node test/validateModuleExportsMatchCommonJS/index.js as our formal npm run test script in the package.json.

But for real, since there's 0 dependencies and 0 build step, passing --provenance to npm publish should be just as strong a guarantee, since there's no additional build steps to guarantee the integrity of.

ianlewis commented 1 year ago

But for real, since there's 0 dependencies and 0 build step, passing --provenance to npm publish should be just as strong a guarantee, since there's no additional build steps to guarantee the integrity of.

You're right this is a bit of a special case so you're right that the security benefits are a bit nuanced for sure. I still think there is a bit of benefit to separating the build, provenance generation, provenance signing, and publishing steps and should work nicely if you ever do need to introduce any build steps or dependencies (though I imagine the later at least is unlikely).