Do you use GitHub Actions to deploy your plugin to the WordPress.org plugin directory? Add this action to your deployment workflow to generate a build provenance attestation of the plugin ZIP file on WordPress.org.
This action integrates well with the WordPress Plugin Deploy action, but it can work with any workflow which deploys your plugin.
Artifact attestations enable you to increase the supply chain security of your builds by establishing where and how your software was built.
This action generates an artifact attestation for the ZIP file that is served by the plugin directory for each release of your plugin. This can subsequently be used by consumers to verify that a given version of your plugin actually originated from your user account on GitHub.
There is not much tooling for the verification aspect at the moment — other than the gh attestation verify
command — but this ultimately facilitates verifying that a plugin release came from its trusted author rather than an unwanted entity, for example somebody who stole your SVN password, hacked into WordPress.org, or performed a hostile plugin takeover.
Within the GitHub Actions workflow which deploys your plugin to the plugin directory:
-
Ensure that at least the following permissions are set:
permissions: id-token: write attestations: write
-
Add the following step to your workflow so it runs after your plugin has been deployed:
- uses: johnbillion/[email protected] with: zip-path: my-plugin-slug.zip
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
permissions:
attestations: write
contents: read
id-token: write
timeout-minutes: 70
steps:
- name: Deploy to the plugin directory
uses: 10up/action-wordpress-plugin-deploy@v2
id: deploy
env:
SVN_USERNAME: ${{ secrets.WPORG_SVN_USERNAME }}
SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }}
with:
generate-zip: true
- name: Generate build provenance attestation
uses: johnbillion/[email protected]
with:
zip-path: ${{ steps.deploy.outputs.zip-path }}
Here is the full list of required and optional inputs:
- uses: johnbillion/[email protected]
with:
# Required. Path to the ZIP file generated for the plugin release.
# Use `${{ steps.deploy.outputs.zip-path }}` if you're using the
# "WordPress.org Plugin Deploy" action.
zip-path: my-plugin-slug.zip
# Optional. Plugin slug name. Default is the repo name.
plugin: my-plugin-slug
# Optional. Plugin version number. Default is the tag name if
# triggered by pushing a tag or creating a release.
version: 1.2.3
# Optional. Maximum time in minutes to spend trying to fetch the
# ZIP from the plugin directory. Default is 60.
timeout: 60
# Optional. Whether to perform a dry run which runs everything
# except for generating the actual attestation. Default false.
dry-run: false
# Optional. The URL where the plugin ZIP file is hosted (for
# platforms other than WordPress.org). Default is the URL of
# the ZIP file on the WordPress.org plugin directory.
zip-url: 'https://example.com/%plugin%-%version%.zip'
Name | Description | Example |
---|---|---|
attestation-id |
GitHub ID for the attestation | 123456 |
attestation-url |
URL for the attestation summary | https://github.com/foo/bar/attestations/123456 |
bundle-path |
Absolute path to the file containing the generated attestation | /tmp/attestation.json |
zip-url |
URL where the plugin ZIP file is hosted | https://downloads.wordpress.org/plugin/foo.1.2.3.zip |
This action is a wrapper for the actions/attest-build-provenance
action provided by GitHub. It specifically handles generating an attestation for the ZIP file of your plugin once it's been deployed to the plugin directory. This facilitates consumers being able to verify the provenance of the ZIP file that they download from WordPress.org, not just for an artifact on GitHub.
Yes, this action supports plugins that have a build step because it is only concerned about whatever you commit to the plugin directory. Just call this action with a ZIP of those files and you're good to go.
Yes, this action specifically supports plugin release confirmation. It will periodically attempt to fetch the plugin ZIP from the plugin directory for up to 60 minutes, which allows you plenty of time to confirm the release.
Tip
Set the timeout-minutes
directive to a little higher than the timeout
input of the action, which is 60 minutes by default. This allows some leeway for generating the attestation if you confirm your release right before the timeout is reached. 70 is a reasonable value.
Yes, this action supports hosts other than WordPress.org in case you want to generate an attestation for a ZIP file that you deploy elsewhere. The zip-url
input can be used to specify a custom ZIP URL to fetch and attest. These dynamic value placeholders can be used within the URL:
%plugin%
for the plugin slug%version%
for the version number
The default ZIP URL is https://downloads.wordpress.org/plugin/%plugin%.%version%.zip
.
If you deploy your plugin to multiple locations, call this action once for each.
At a minimum you need to know the name of the owner of the repo that the plugin was built from, for example johnbillion
.
Then you can fetch the plugin ZIP file at a specific version and verify its provenance using the gh
command:
The --owner
option works regardless of whether or not the plugin uses a reusable workflow for its deployment:
wget https://downloads.wordpress.org/plugin/query-monitor.3.16.4.zip
gh attestation verify query-monitor.3.16.4.zip \
--owner johnbillion
The --repo
option only works only if the plugin is not using a reusable workflow for its deployment:
wget https://downloads.wordpress.org/plugin/query-monitor.3.16.4.zip
gh attestation verify query-monitor.3.16.4.zip \
--repo johnbillion/query-monitor
The combined --repo
and --signer-repo
options work if the plugin uses a reusable workflow for its deployment:
wget https://downloads.wordpress.org/plugin/query-monitor.3.16.4.zip
gh attestation verify query-monitor.3.16.4.zip \
--repo johnbillion/query-monitor \
--signer-repo johnbillion/plugin-infrastructure
Create a workflow_dispatch
workflow that calls the johnbillion/action-wordpress-plugin-attestation
action with the zip file of your plugin. You can then run this workflow against a branch or tag of your choice from the Actions screen of your repo.
Optionally use the dry-run
parameter to perform all the verification steps without publishing the attestation.
Example workflow:
name: Test attestation
on:
workflow_dispatch:
jobs:
deploy:
name: Test attestation
runs-on: ubuntu-latest
permissions:
attestations: write
contents: read
id-token: write
steps:
- name: Build the plugin zip file without deploying it
uses: 10up/action-wordpress-plugin-deploy@v2
id: deploy
with:
generate-zip: true
dry-run: true
- name: Generate build provenance attestation
uses: johnbillion/[email protected]
with:
zip-path: ${{ steps.deploy.outputs.zip-path }}
dry-run: true # Remove this to publish the attestation
See above.
To the best of my understanding, build provenance attestation on GitHub facilitates adhering to SLSA v1.0 Build Level 2.
Adhering to SLSA v1.0 Build Level 3 requires that the build runs in an isolated environment. One way to do this is to use a reusable workflow to perform the build, deployment, and attestation generation, but there are additional considerations such as not using caching during the build and deployment process.
The action will output a link to the attestation.
You can also view all attestations from the Actions -> Attestations screen in your repo.
Yes, but be aware that when a consumer uses gh attestation verify
to verify an attestation they need to be aware of which option(s) they need to provide to the command depending on whether a reusable workflow was used to deploy the plugin. See the How do I verify a plugin that publishes attestations? section above for all the details.
The time that I spend maintaining this library and others is in part sponsored by:
Plus all my kind sponsors on GitHub:
MIT